Plugin

keycuts.js

Small, opinionated keyboard shortcuts as a drop-in plugin. Smart defaults, conflict detection, input guard, auto-rendered help panel — no framework, no build step.

npm install @goboldlyforward/keycuts

Playground

Try the shortcuts

Press ⌘K / Ctrl+K to jump to search · ⇧? to toggle the help overlay · E to edit · A to archive · S then a typo-resistant ⌘S to save. The input guard means E/A/S won't fire while you're typing in the search box.

Last key — press any key —

Log shows every keycuts:invoke the plugin fires.

Inline help

This panel is mounted with cuts.mountList(...) in inline mode, so it always renders. The overlay (toggled by ⇧?) uses the same renderer in modal mode.

Defaults

Three shortcuts, ready to go

Constructing new Keycuts() with no arguments wires up cmd+k (focuses anything with [data-keycuts-search]), shift+? (toggles mounted help panels), and esc (clicks the closest [data-keycuts-close]).

| Keys      | Command                |
| --------- | ---------------------- |
| cmd+k     | Focus search           |
| shift+?   | Toggle help            |
| esc       | Close dialog or panel  |
// Defaults on, no extra config.
const cuts = new Keycuts();

// Skip the defaults and start empty:
const empty = new Keycuts({ defaults: false });

Custom

Register your own

Pass an id, the keys string (any modifier order — Shift+Cmd+P normalizes to meta+shift+p), and a selector or handler. Built-in actions: click (default), focus, submit, keycuts:toggle-help.

cuts.add({
  id: 'new_project',
  keys: 'cmd+n',
  description: 'New project',
  group: 'Projects',
  selector: '[data-command="new-project"]',
});

// Or run a handler:
cuts.add({
  id: 'reload',
  keys: 'cmd+r',
  description: 'Reload data',
  handler: () => refreshData(),
});
// Cherry-pick from the common catalog
cuts.add(Keycuts.COMMON.find(s => s.id === 'edit'));
cuts.add(Keycuts.COMMON.find(s => s.id === 'archive'));
cuts.add(Keycuts.COMMON.find(s => s.id === 'save'));
cuts.add(Keycuts.COMMON.find(s => s.id === 'command_palette'));

Input guard

Plain keys don't fire while you're typing

Focus the search box at the top of the playground, then mash E and A. The shortcut won't fire — the input guard skips plain-key shortcuts inside input, textarea, select, contenteditable, and role="textbox". Combos with modifiers (cmd+s) still fire because they're unambiguous; opt in per-shortcut with allowInInputs: true.

Conflicts

Caught at register-time

Registering an id or keys combo that's already bound throws — the message tells you which one. Pass { replace: true } (or use cuts.replace(...)) to overwrite intentionally.

cuts.add({ id: 'search', keys: 'cmd+/', description: 'Search', selector: '...' });
// Error: Keycuts: id search already registered (was meta+k).
//   Pass { replace: true } to overwrite intentionally.

cuts.replace({ id: 'search', keys: 'cmd+/', description: 'Search', selector: '...' });
// ✓ replaces in place