Skip to content
tactile

Limitations

Keyboard handling on the web has hard edges. Tactile is honest about them so you don’t waste time on unfixable bugs.

These are intercepted before your page sees them. No library can reliably override them:

Platform Examples
macOS Cmd+W (close tab), Cmd+Q (quit), Cmd+T (new tab), Cmd+Tab
Windows/Linux Alt+F4, Ctrl+W, browser chrome shortcuts
All F11 (fullscreen), F5 (refresh) in many browsers

Don’t bind these. Pick alternatives (mod+shift+p for palette, etc.).

Physical vs logical — no universal default

Section titled “Physical vs logical — no universal default”

On Dvorak, AZERTY, or QWERTZ layouts, “the Z key” and “the letter Z” diverge. Hybrid mode picks keycap position for letters — the industry convention — but some users expect character matching.

  • Default: keycap position for letters
  • Override: match: 'logical' per rule or defaultMatch: 'logical' globally

See International keyboards.

format() and getKeymap().labels show labels derived from the binding string (⌘K for mod+k). They do not automatically show what a keycap types on Dvorak or AZERTY.

VS Code solves this with layout-aware labels + warnings. Tactile keeps labels simpler — see International keyboards for the convention to note in your help UI.

Most punctuation has a layout-dependent code. In physical mode, symbol tokens fall back to character matching. This is documented behavior, not a bug.

By default, shortcuts don’t fire while typing in input, textarea, select, or contenteditable. Strategies for overriding this live in Engine options → ignore.

If you disable the ignore predicate, you must gate shortcuts with when expressions (e.g. !textInputFocus) — the VS Code approach.

Tactile does not automatically suppress shortcuts during IME composition. VS Code sets an isComposing context key; you can do the same:

element.addEventListener('compositionstart', () => {
kb.context.set('composing', true);
});
element.addEventListener('compositionend', () => {
kb.context.delete('composing');
});
kb.add({
id: 'palette',
keys: 'mod+k',
when: '!composing',
handler: openPalette,
});

Held keys emit repeated keydown events (event.repeat === true). Tactile ignores repeats for sequence buffering but single-chord handlers can still fire on repeat. Debounce in your handler if needed:

handler: (event) => {
if (event.repeat) return;
togglePalette();
},
  • AltGraph is not distinguished from Alt — altKey is one boolean.
  • Numpad Enter (NumpadEnter) and main Enter (Enter) are separate code values. The enter token matches event.key === 'Enter' (logical), which typically covers both — but physical-only matching would treat them differently.

VS Code supports shift shift (double-tap Shift) via keyup timing. Tactile supports keyup bindings via eventType: 'keyup' but does not implement double-tap modifier detection.

The default listener is on document. Events from focused iframes may not reach your page’s listener depending on origin. Shadow DOM events retarget — event.target may not be the inner element.

Tactile is pre-1.0. The API is settling. Pin versions in production and watch the changelog.