Skip to content

API Overview ​

Core API (Most Users) ​

SymbolPurposeExecution modeCommon gotcha
createKeymap()Create shortcut manager from an ordered binding arraySyncdispose() is terminal
findShortcutConflicts()Find duplicate and prefix pathsSyncInvalid non-empty input throws
formatShortcut()Format shortcut labelsSyncInvalid input returns ''
matchKey()Match an event against one shortcut patternSyncUnparseable pattern returns false; chord patterns never match
detectModKey()Resolve platform primary modifierSyncReturns ctrl without navigator
tap()Observe chord lifecycle, matches, and disposalSyncObserver failures are swallowed
BindingPer-binding config type (id, shortcut, handler, trigger, when, preventDefault, stopPropagation)N/AReusing an id replaces its binding

Parser Subpath (@vielzeug/keymap/parse) ​

Use the parser subpath if you're building keyboard-aware config validators, custom UI, or framework integrations.

SymbolPurposeExecution modeCommon gotcha
parseShortcut()Strictly parse full shortcutSyncEmpty input throws
parseStep()Parse one step without throwingSyncInvalid input returns null
canonicalizeShortcut()Create stable shortcut keySyncInput must already be parsed
matchStep()Test event against parsed stepSyncExtra modifiers prevent a match
detectModKey()Resolve platform primary modifierSyncReturns ctrl without navigator

Errors ​

SymbolPurposeExecution modeCommon gotcha
KeymapErrorBase Keymap errorSyncIncludes parse, config, and lifecycle errors
KeymapConfigErrorInvalid createKeymap optionSyncThrown for a non-positive chordTimeout
KeymapParseErrorStrict parser errorSyncparseStep() never throws it

Package Entry Points ​

ImportPurpose
@vielzeug/keymapRoot entry point for createKeymap, formatShortcut, findShortcutConflicts, matchKey, detectModKey, errors, and public types.
@vielzeug/keymap/parseParser internals subpath for custom tooling: parseShortcut, parseStep, matchStep, matchKey, canonicalizeShortcut, detectModKey.

Core Manager ​

createKeymap() ​

ts
function createKeymap(
  bindings?: readonly Binding[],
  options?: KeymapOptions,
): Keymap;

Creates shortcut manager with independent chord state for each mounted target.

ParameterTypeDescription
bindingsreadonly Binding[]Ordered array of bindings. Each binding has an explicit id, shortcut, and handler.
optionsKeymapOptionsChord, modifier, and global-guard configuration.

Returns: Keymap.

ts
import { createKeymap } from '@vielzeug/keymap';

const map = createKeymap([
  { id: 'save', shortcut: 'ctrl+s', handler: () => console.log('save') },
]);
const unmount = map.mount(document);

unmount();
map.dispose();
Keymap memberReturnContract
bind(binding)() => voidAdds or replaces a binding with the same id. Returned callback removes that binding while active.
mount(target)() => voidAdds target listener. Repeat mounts of same target are reference-counted.
unbind(id)voidRemoves binding by id. Warns in development when unknown.
listBindings()readonly BindingEntry[]Returns a detached binding snapshot.
tap(handler, options?)() => voidObserves chord lifecycle, matches, and disposal. Supports signal-owned teardown.
dispose()voidRemoves all listeners, aborts signal, and permanently disposes map. Idempotent.
disposedbooleantrue after first dispose().
disposalSignalAbortSignalAborts when map is disposed.
[Symbol.dispose]()voidCalls dispose().

After disposal, bind(), unbind(), and mount() throw KeymapError.

Runtime Observation ​

tap() ​

ts
function tap(
  handler: (event: KeymapEvent) => void,
  options?: { signal?: AbortSignal },
): () => void;

Observes chord progress, cancellation, completed matches, timeouts, and disposal without affecting shortcut behavior.

ParameterTypeDescription
handler(event: KeymapEvent) => voidReceives each runtime event synchronously. Errors are swallowed.
options.signalAbortSignalDetaches the handler when aborted.

Returns: An idempotent detach function.

ts
const stop = map.tap((event) => {
  if (event.type === 'chord-start') showChordHint(event.step);
  if (event.type === 'chord-cancel' || event.type === 'chord-timeout' || event.type === 'match') hideChordHint();
});

Conflict Analysis ​

findShortcutConflicts() ​

ts
function findShortcutConflicts(
  shortcut: string,
  entries: readonly BindingEntry[],
  options?: ConflictOptions,
): BindingEntry[];

Returns entries with same-trigger exact or prefix-conflicting shortcut paths.

ParameterTypeDescription
shortcutstringProposed shortcut. Empty or whitespace-only input returns no conflicts.
entriesreadonly BindingEntry[]Bindings to compare, commonly map.listBindings().
optionsConflictOptionsOptional modifier resolution and trigger filter.

Returns: Matching entries. Returns [] when no conflict exists.

ts
import { createKeymap, findShortcutConflicts } from '@vielzeug/keymap';

const map = createKeymap([{ id: 'top', shortcut: 'g', handler: () => console.log('top') }]);
const conflicts = findShortcutConflicts('g g', map.listBindings());

console.log(conflicts.length); // 1

Formatting ​

formatShortcut() ​

ts
function formatShortcut(shortcut: string, modKey?: 'ctrl' | 'meta'): string;

Formats parsed shortcut into Mac symbols for meta or word labels for ctrl.

ParameterTypeDescription
shortcutstringShortcut string to format.
modKey'ctrl' | 'meta'Platform primary modifier. Defaults to detectModKey().

Returns: Display label, or '' for invalid input.

ts
import { formatShortcut } from '@vielzeug/keymap';

formatShortcut('mod+shift+p', 'meta'); // ⇧⌘P
formatShortcut('mod+shift+p', 'ctrl'); // Ctrl+Shift+P

Parser Subpath (@vielzeug/keymap/parse) ​

parseShortcut() ​

ts
function parseShortcut(raw: string, modKey?: 'ctrl' | 'meta'): Shortcut;

Strictly parses one or more space-separated shortcut steps.

ParameterTypeDescription
rawstringFull shortcut string.
modKey'ctrl' | 'meta'Platform primary modifier. Defaults to detectModKey().

Returns: Parsed Shortcut.

ts
import { parseShortcut } from '@vielzeug/keymap/parse';

const shortcut = parseShortcut('ctrl+k ctrl+s', 'ctrl');
console.log(shortcut.length); // 2

Throws KeymapParseError for empty, modifier-only, or ambiguous steps.


parseStep() ​

ts
function parseStep(raw: string, modKey?: 'ctrl' | 'meta'): ShortcutStep | null;

Parses one shortcut step without throwing.

ParameterTypeDescription
rawstringOne shortcut step.
modKey'ctrl' | 'meta'Platform primary modifier. Defaults to detectModKey().

Returns: Parsed ShortcutStep, or null for empty, modifier-only, or ambiguous input.

ts
import { parseStep } from '@vielzeug/keymap/parse';

parseStep('ctrl+k', 'ctrl'); // { key: 'k', modifiers: Set(['ctrl']) }
parseStep('ctrl+k+j', 'ctrl'); // null

canonicalizeShortcut() ​

ts
function canonicalizeShortcut(steps: readonly ShortcutStep[]): string;

Converts parsed steps into stable canonical string with sorted modifier order.

ParameterTypeDescription
stepsreadonly ShortcutStep[]Parsed shortcut steps.

Returns: Canonical shortcut string.

ts
import { canonicalizeShortcut, parseShortcut } from '@vielzeug/keymap/parse';

canonicalizeShortcut(parseShortcut('shift+ctrl+k', 'ctrl')); // ctrl+shift+k

matchStep() ​

ts
function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean;

Tests exact key and modifier equality for one parsed step.

ParameterTypeDescription
eventKeyboardEventEvent to match. Missing runtime key returns false.
stepShortcutStepParsed step.

Returns: true only when key and all modifier states match.

ts
import { matchStep, parseStep } from '@vielzeug/keymap/parse';

const step = parseStep('ctrl+k', 'ctrl')!;
matchStep(new KeyboardEvent('keydown', { ctrlKey: true, key: 'k' }), step); // true

matchKey() ​

ts
function matchKey(event: KeyboardEvent, pattern: string, modKey?: 'ctrl' | 'meta'): boolean;

Matches a keyboard event against a single shortcut pattern such as 'ArrowDown', 'esc', 'mod+k', or 'shift+Home'. Aliases and mod resolve through the same parser createKeymap uses, and modifier state must match exactly: 'ArrowDown' does not match Ctrl+ArrowDown. Exported from both the root entry point and @vielzeug/keymap/parse.

ParameterTypeDescription
eventKeyboardEventEvent to match. Missing runtime key returns false.
patternstringOne shortcut step. Unparseable patterns and multi-step chords return false (never throws).
modKey'ctrl' | 'meta'Optional. Resolution for mod; defaults to detectModKey().

Returns: true only when key and all modifier states match.

ts
import { matchKey } from '@vielzeug/keymap';

if (matchKey(event, 'mod+k')) openPalette();
if (matchKey(event, 'shift+Home')) selectToStart(event);

detectModKey() ​

ts
function detectModKey(): 'ctrl' | 'meta';

Detects Mac platform from navigator and otherwise returns ctrl.

Returns: 'meta' on Mac platforms; 'ctrl' elsewhere or without navigator.

ts
import { detectModKey } from '@vielzeug/keymap/parse';

const modKey = detectModKey();

Types ​

Keymap ​

Stateful shortcut manager returned by createKeymap().

ts
interface Keymap {
  [Symbol.dispose](): void;
  bind(binding: Binding): () => void;
  dispose(): void;
  readonly disposalSignal: AbortSignal;
  readonly disposed: boolean;
  listBindings(): readonly BindingEntry[];
  mount(target: EventTarget): () => void;
  tap(handler: (event: KeymapEvent) => void, options?: { signal?: AbortSignal }): () => void;
  unbind(id: string): void;
}

KeymapOptions ​

Options applied to every binding owned by one manager.

ts
interface KeymapOptions {
  chordTimeout?: number;
  modKey?: 'ctrl' | 'meta';
  when?: When;
}
  • when: Guard function called for all bindings. When combined with per-binding when guards, both must return true for the handler to fire (AND composition). Global guard is checked first.
  • chordTimeout: Must be a positive finite number; createKeymap() throws KeymapConfigError otherwise. Defaults to 1000.

KeymapEvent ​

Runtime event observed through tap().

ts
type KeymapEvent =
  | { type: 'chord-cancel'; target: EventTarget; trigger: 'keydown' | 'keyup' }
  | { type: 'chord-start'; step: ShortcutStep; target: EventTarget; trigger: 'keydown' | 'keyup' }
  | { type: 'chord-progress'; steps: readonly ShortcutStep[]; target: EventTarget; trigger: 'keydown' | 'keyup' }
  | { type: 'chord-timeout'; target: EventTarget; trigger: 'keydown' | 'keyup' }
  | { type: 'match'; binding: BindingEntry; target: EventTarget; trigger: 'keydown' | 'keyup' }
  | { type: 'dispose' };

step, steps, and binding are detached snapshots. Mutating them does not affect matching.

Binding ​

Per-binding configuration. Each binding has an explicit id so duplicate shortcuts can coexist. Reusing an ID replaces its binding; among duplicate shortcuts, the first binding whose guard passes wins.

ts
interface Binding {
  id: string;
  shortcut: string;
  handler: Handler;
  trigger?: 'keydown' | 'keyup';
  when?: When;
  preventDefault?: boolean;
  stopPropagation?: boolean;
}
  • trigger: Defaults to 'keydown'.
  • preventDefault: Defaults to true. Set to false for shortcuts that must retain browser behavior.
  • stopPropagation: Defaults to false.

Handler and When ​

ts
type Handler = (event: KeyboardEvent) => void;
type When = (event: KeyboardEvent) => boolean;

BindingEntry ​

Detached binding metadata returned by listBindings().

ts
type BindingEntry = {
  readonly id: string;
  readonly shortcut: readonly ShortcutStep[];
  readonly trigger: 'keydown' | 'keyup';
  readonly preventDefault: boolean;
  readonly stopPropagation: boolean;
};

ModifierKey, Shortcut, and ShortcutStep ​

Parser types used by parseShortcut(), parseStep(), matchStep(), and canonicalizeShortcut(). Available from @vielzeug/keymap/parse.

ts
type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';

type ShortcutStep = {
  key: string;
  modifiers: Set<ModifierKey>;
};

type Shortcut = ShortcutStep[];

ConflictOptions ​

Comparison options for findShortcutConflicts().

ts
interface ConflictOptions {
  modKey?: 'ctrl' | 'meta';
  trigger?: 'keydown' | 'keyup';
}

Errors ​

ErrorTriggerNotable properties
KeymapErrorLifecycle operation after disposalUse instanceof KeymapError to narrow Keymap errors.
KeymapConfigErrorcreateKeymap() receives an invalid optionExtends KeymapError. Thrown for a non-positive or non-finite chordTimeout.
KeymapParseErrorStrict shortcut parser receives invalid inputExtends KeymapError.