Skip to content
tactile

Quick start

Wrap your app once, then register shortcuts anywhere with useShortcut.

app.tsx
import { KeybindProvider } from '@tactile-js/react';
export function App() {
return (
<KeybindProvider>
<Editor />
</KeybindProvider>
);
}
editor.tsx
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.

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')}
/>
);
}
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 rule
kb.dispose(); // detach all listeners
Topic Doc
code vs key, Dvorak, QWERTZ Match modesInternational keyboards
Binding strings, sequences, mod Key syntax
Context gating When expressions
Full API Core API reference
Real-world patterns Recipes
Try live Playground