Skip to content
tactile

Core API reference

The core package is framework-agnostic. Most apps need createKeybindingEngine and the types; lower-level exports exist for custom adapters, inspectors, and settings UIs.

Creates an engine instance. This is a factory, not a global singleton — each instance owns its own registry, context, and listeners.

import { createKeybindingEngine } from '@tactile-js/core';
const kb = createKeybindingEngine({
defaultMatch: 'hybrid',
sequenceTimeout: 1000,
debug: false,
});

Returns a KeybindingEngine. See Engine options for every option.


Register a rule. Returns an unbind function.

const off = kb.add({
id: 'palette.open',
keys: 'mod+k',
when: "scope == 'global'",
group: 'Navigation',
description: 'Open command palette',
priority: 0,
handler: (event, info) => {
openPalette();
// return false to skip preventDefault()
},
});
off(); // unregister

The context-key store driving when expressions. See Context & when expressions.

kb.context.set('scope', 'editor');
kb.context.set('readOnly', false);
kb.context.get('scope'); // 'editor'
kb.context.has('readOnly'); // true
kb.context.delete('readOnly');
kb.context.assign({ scope: 'global', modalOpen: false });
kb.context.snapshot(); // { scope: 'global', modalOpen: false }
const unsub = kb.context.subscribe(() => {
console.log('context changed', kb.context.snapshot());
});

Every registered rule with platform-formatted labels — the source of truth for help dialogs.

kb.getKeymap();
// [{
// id: 'palette.open',
// keys: ['mod+k'],
// labels: ['⌘K'], // or ['Ctrl+K'] on Windows/Linux
// when: "scope == 'global'",
// group: 'Navigation',
// description: 'Open command palette',
// priority: 0,
// }]

getKeymapForContext(snapshot): ResolvedBinding[]

Section titled “getKeymapForContext(snapshot): ResolvedBinding[]”

Like getKeymap(), but only rules whose when holds in the given snapshot.

const active = kb.getKeymapForContext(kb.context.snapshot());

Rules competing for the same keystroke. See Collision detection.

Capture the next complete keystroke. Returns a stop function. Used for “press a key to rebind” UIs.

const stop = kb.recordShortcut(({ binding, label }) => {
console.log(binding); // 'ctrl+shift+k'
console.log(label); // '⇧⌘K' on macOS
});
// stop(); // cancel without capturing

Render a binding string as a display label.

kb.format('mod+shift+p'); // '⇧⌘P' on macOS, 'Ctrl+Shift+P' elsewhere

Notified when rules are added or removed.

Detach all listeners and reset internal state.


Field Type Default Description
id string required Stable command id, e.g. 'palette.open'.
keys string | string[] required Binding string or alternatives. See Key syntax.
handler KeybindingHandler required Called on match. Return false to skip preventDefault.
when string Boolean expression over context keys.
priority number 0 Higher wins collisions.
group string Category for help UIs.
description string Human-readable label for help UIs.
match 'hybrid' | 'physical' | 'logical' engine default Per-rule match mode override.
enableInFormFields boolean false Fire even while typing in inputs/textareas/contenteditable. Chords only — see Engine options → ignore.
preventDefault boolean | (event) => boolean true Whether to call preventDefault on match.
eventType 'keydown' | 'keyup' 'keydown' Which event to listen on.
type KeybindingHandler = (
event: KeyEvent,
info: MatchInfo,
) => void | boolean;
Field Type Description
id string The matched rule’s id.
keys string The specific binding string that matched.
sequence boolean true if match came from a multi-stroke sequence like g i.

Structural subset of DOM KeyboardEvent — real browser events satisfy it; tests can pass plain objects.

Field Type
key string
code string
ctrlKey, shiftKey, altKey, metaKey boolean
repeat boolean (optional)
target EventTarget | null (optional)
preventDefault () => void (optional)

Output of getKeymap() — a rule plus presentation data.

interface Collision {
keys: string;
rules: Array<{ id: string; when?: string; priority: number }>;
}
interface RecordedShortcut {
binding: string; // canonical, re-parseable: 'ctrl+shift+k'
label: string; // display: '⇧⌘K'
}

Exported for tooling and custom adapters:

Export Purpose
createDomSource(target?) Default DOM event source bound to document.
defaultIgnore(event) Default predicate that skips inputs and contenteditable.
ContextStore Standalone context store (also available as engine.context).
parseBinding(input, platform) Parse a binding string into chords.
formatBinding(binding, platform) Format a binding for display.
matchChord(chord, event, mode) Match a single chord against an event.
compileWhen(expression) Parse a when string into an AST (cached).
evaluateWhen(node, snapshot) Evaluate a compiled when AST.
matchWhen(expression, snapshot) Compile + evaluate in one call.
detectPlatform() 'mac' or 'other'.
resolveMod(platform) 'meta' on Mac, 'ctrl' elsewhere.
KeybindingParseError Thrown on invalid binding strings.
WhenExpressionError Thrown on invalid when expressions.

For non-DOM hosts (Electron, tests), inject a custom event source — see Engine options → source.


const kb = createKeybindingEngine();
// Register rules
const rules = [
kb.add({ id: 'a', keys: 'mod+k', handler: a }),
kb.add({ id: 'b', keys: 'mod+s', handler: b }),
];
// Update context as UI state changes
kb.context.set('scope', 'editor');
// Introspect
kb.getKeymap();
kb.getCollisions();
// Cleanup
rules.forEach((off) => off());
kb.dispose();
  • Engine options — every constructor option in depth
  • Key syntax — everything keys accepts
  • Recipes — the API applied to palettes, help dialogs, and rebinding UIs