Skip to content
tactile

Engine options

Pass options to createKeybindingEngine() or <KeybindProvider options={…}>.

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

Where key events come from. Defaults to a DOM source on document.

Override for tests, Electron, or embedded editors:

const kb = createKeybindingEngine({
source: {
on(type, listener) {
const h = (e: KeyboardEvent) => listener(e);
document.addEventListener(type, h);
return () => document.removeEventListener(type, h);
},
},
});

The engine calls source.on('keydown', …) and source.on('keyup', …) once at creation. Rules are added/removed from the registry — listeners are not re-bound per rule.

Default match strategy for rules that don’t set their own match. Defaults to 'hybrid'. The full mode-by-key-type behavior table lives in Match modes.

// Character-based matching for every rule
const kb = createKeybindingEngine({ defaultMatch: 'logical' });

Per-rule override:

kb.add({ id: 'vim.j', keys: 'j', match: 'physical', handler: down });

Maximum gap in milliseconds between strokes of a multi-key sequence. Defaults to 1000.

kb.add({ id: 'inbox', keys: 'g i', handler: goToInbox });
// 'g' then 'i' within 1000ms

Increase for slower users or accessibility:

createKeybindingEngine({ sequenceTimeout: 3000 });

Predicate returning true when the event should be skipped. Defaults to defaultIgnore, which returns true for:

  • <input>
  • <textarea>
  • <select>
  • contenteditable elements
import { createKeybindingEngine, defaultIgnore } from '@tactile-js/core';
// Never ignore — shortcuts fire everywhere (like VS Code's window listener)
const kb = createKeybindingEngine({ ignore: () => false });

The common case — a command palette that must open from anywhere, including text fields — doesn’t need a custom predicate. Opt the rule in:

kb.add({
id: 'palette',
keys: 'mod+k',
enableInFormFields: true,
handler: openPalette,
});

Everything else stays paused while the user types. Two guardrails are built in: sequences never fire inside form fields (they’re indistinguishable from typing), and debug: true warns if you set this on a binding without a ctrl/alt/meta modifier, since plain keys would collide with text entry.

Allow shortcuts by predicate (engine-wide)

Section titled “Allow shortcuts by predicate (engine-wide)”

For broader policies, replace the predicate:

createKeybindingEngine({
ignore: (event) => {
if (!defaultIgnore(event)) return false;
// Let mod+s through even in inputs
if (event.metaKey && event.code === 'KeyS') return false;
return true;
},
});

Gate shortcuts with when instead of a global ignore:

kb.add({
id: 'editor.save',
keys: 'mod+s',
when: 'editorFocus',
handler: save,
});
// In your editor component on focus/blur:
kb.context.set('editorFocus', true);

Override platform detection ('mac' | 'other'). Auto-detected from navigator when omitted.

Affects:

  • mod resolution (meta vs ctrl)
  • format() label output (⌘K vs Ctrl+K)
// Force Windows-style labels on any OS (e.g. for screenshots)
kb.format('mod+k', 'other'); // 'Ctrl+K'

Log registrations, collisions, and dispatches to the console. Defaults to false.

const kb = createKeybindingEngine({ debug: true });
// [keybind] registered palette.open (mod+k)
// [keybind] collision on "mod+k": a vs b
// [keybind] mod+k -> palette.open (won over 1)

Useful during development. Disable in production.