Quick start
Wrap your app once, then register shortcuts anywhere with useShortcut.
import { KeybindProvider } from '@tactile-js/react';
export function App() { return ( <KeybindProvider> <Editor /> </KeybindProvider> );}import { useShortcut } from '@tactile-js/react';
function Editor() { useShortcut({ id: 'palette.open', keys: 'mod+k', // ⌘K on macOS, Ctrl+K elsewhere group: 'Navigation', description: 'Open command palette', handler: () => openPalette(), });
return <main />;}The handler is read through a ref — passing a fresh closure every render does not re-register the binding. It always sees current state.
Scoped shortcut
Section titled “Scoped shortcut”import { useShortcut, useScope } from '@tactile-js/react';
function Editor() { const { setScope } = useScope();
useShortcut({ id: 'editor.bold', keys: 'mod+b', when: "scope == 'editor'", handler: () => toggleBold(), });
return ( <div tabIndex={0} onFocus={() => setScope('editor')} onBlur={() => setScope('global')} /> );}Vanilla / framework-agnostic
Section titled “Vanilla / framework-agnostic”import { createKeybindingEngine } from '@tactile-js/core';
const kb = createKeybindingEngine();
const off = kb.add({ id: 'file.save', keys: 'mod+s', when: "scope == 'editor'", group: 'File', description: 'Save', handler: () => save(),});
kb.context.set('scope', 'editor');
off(); // unregister this rulekb.dispose(); // detach all listenersWhat to read next
Section titled “What to read next”| Topic | Doc |
|---|---|
code vs key, Dvorak, QWERTZ |
Match modes → International keyboards |
Binding strings, sequences, mod |
Key syntax |
| Context gating | When expressions |
| Full API | Core API reference |
| Real-world patterns | Recipes |
| Try live | Playground |