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.
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