keysmith

A shortcut manager,
not a key binder.

keysmith gives shortcuts stable command identity: keys attach to commands, scopes decide where they fire, and users can remap or disable any of them. Framework agnostic. No UI. One dependency.

bun add @zandoh/keysmith

This strip is keysmith running on this page. Press some keys — try g d, or ? for the cheatsheet.

Commands, not callbacks on keys

Stable identity

Handlers subscribe to file.save, not mod+s. Remapping changes keys, never code.

Scopes

A binding belongs to a scope; activate and deactivate them as your UI changes. Collisions are reported, not silent.

Sequences

g i, mod+k mod+s — chords and sequences share one recognizer with timeouts.

Correct on every layout

IME and dead keys never match. AltGr text entry is never stolen. Alt chords survive macOS composition. Symbols match with or without shift.

Users can remap anything

WCAG 2.1.4 requires single-character shortcuts to be remappable or disableable. keysmith's keymap layer is that mechanism — try it on the forge.ping command:

forge.ping is bound to mod+j
exportKeymap()
{}

forge.ping has fired 0 times. Press its binding.

Quick start

import { createKeysmith } from "@zandoh/keysmith";

const keys = createKeysmith();

keys.add({
  id: "file.save",
  keys: "mod+s", // Meta on macOS, Control elsewhere
  description: "Save the current file",
  onTrigger: () => save(),
});

keys.add({ id: "go.inbox", keys: "g i", scope: "list" });
keys.on("go.inbox", () => router.push("/inbox"));
keys.activate("list");

// user remapping, persisted
keys.remap("go.inbox", "g m");
localStorage.setItem("keymap", JSON.stringify(keys.exportKeymap()));

// data for your cheatsheet: descriptions, groups, "⌘S" / "Ctrl+S"
keys.commands();

Shortcuts on this page

Generated live from keys.commands().