Tactile vs hotkeys-js
hotkeys-js is one of the most-used shortcut libraries on
npm, and it deserves it: actively maintained, dependency-free, with a real scope system
(setScope/getScope) and an input filter. Its two structural limits are the deprecated keyCode
key model and scopes that are flat names rather than expressions.
At a glance
Section titled “At a glance”| hotkeys-js | Tactile | |
|---|---|---|
| Key model | keyCode (deprecated) |
event.key + event.code, hybrid |
| Scopes | one active named scope | when expressions — compose freely |
| Conflicts | last binding silently stacks | detected + priority |
| Input filter | global hotkeys.filter |
per-engine ignore |
Sequences (g i) |
✗ | ✅ |
| Introspection | key codes only | full keymap with labels |
| Size | ~2.6 kB | ~5.5 kB |
Scopes vs when-expressions
Section titled “Scopes vs when-expressions”hotkeys-js has exactly one active scope at a time:
hotkeys('ctrl+b', 'editor', toggleBold);hotkeys.setScope('editor');Tactile’s scopes are just context keys, so conditions compose:
kb.add({ id: 'bold', keys: 'ctrl+b', when: "scope == 'editor' && !readOnly && !modalOpen", handler: toggleBold,});With one active scope, “editor shortcuts, but not while a modal is open, but only when editable” forces scope-juggling in app code. With expressions it’s one line on the rule.
Migration
Section titled “Migration”// hotkeys-jshotkeys('ctrl+a,ctrl+b', 'editor', (event, handler) => { if (handler.key === 'ctrl+a') selectAll(); else bold();});// Tactile — one rule per command (ids power help dialogs + collisions)kb.add({ id: 'selectAll', keys: 'ctrl+a', when: "scope == 'editor'", handler: selectAll });kb.add({ id: 'bold', keys: 'ctrl+b', when: "scope == 'editor'", handler: bold });kb.context.set('scope', 'editor'); // replaces hotkeys.setScope('editor')