Skip to content
keymap logoKeymapApp Infrastructure
Target-local keyboard shortcut manager with chords, event-aware guards, modifier aliases, and terminal disposal.
Version
v3.0.0
Size
2.5 KB gzip
Dependencies
Zero dependencies
BrowserNode ≥22SSRDeno
createKeymapfindShortcutConflictsformatShortcutKeymapErrorKeymapParseError

Why Keymap?

Browser keyboard handling needs modifier normalization, chord state, context policy, and listener ownership. Keymap keeps those concerns in one headless, zero-dependency handle.

ts
// Before
window.addEventListener('keydown', (event) => {
  if ((event.ctrlKey || event.metaKey) && event.key === 's') event.preventDefault();
});

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

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

unmount();
map.dispose();
FeatureRaw addEventListenerKeymap
Bundle size0 B (built-in)2.5 KB
Zero dependencies
Chord sequences
Modifier aliasescmd, win, option → canonical
Context guardsManual if in handlerEvent-aware when(event) predicate
Chord ownershipApplication-managed statePer mounted target
Per-binding controlManual preventDefault/stopPropagationPer-binding preventDefault/stopPropagation with safe defaults
DisposableManual removeEventListenerTerminal dispose() + [Symbol.dispose]()

Use Keymap when you need chord sequences (g g, ctrl+k ctrl+s), modifier aliases, or context-scoped hotkeys that can be cleanly mounted and unmounted.

Consider raw addEventListener when you have a single, static, never-removed hotkey and don't need chords.

Installation

sh
pnpm add @vielzeug/keymap
sh
npm install @vielzeug/keymap
sh
yarn add @vielzeug/keymap

Quick Start

Create, mount, then dispose one map owned by your UI scope.

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

const map = createKeymap([
  { id: 'save', shortcut: 'mod+k mod+s', handler: () => console.log('save') },
  { id: 'palette', shortcut: 'mod+shift+p', handler: () => console.log('open palette') },
  { id: 'top', shortcut: 'g g', handler: () => window.scrollTo({ top: 0 }) },
  { id: 'close', shortcut: 'escape', handler: () => console.log('close panel') },
]);

const unmount = map.mount(document);

unmount();
map.dispose();

Features

  • createKeymap() — Create a keymap from an ordered binding array; mount to any EventTarget
  • Chord sequences — "g g", "ctrl+k ctrl+s" with configurable timeout (default 1 s)
  • Modifier aliases — cmd/command/winmeta; opt/optionalt; mod → platform-aware
  • Binding objects — each binding has an explicit id, shortcut, handler, and optional trigger, when, preventDefault, stopPropagation
  • Per-binding preventDefault/stopPropagation — safe defaults (true/false) applied to completed shortcuts and intermediate chord steps
  • tap() — observe chord progress, timeout, matches, and disposal without changing behavior
  • modKey option — explicit platform override for SSR and cross-platform tests
  • formatShortcut() — platform-aware display (⇧⌘P on Mac, Ctrl+Shift+P elsewhere)
  • @vielzeug/keymap/parse subpath — parseShortcut(), parseStep(), matchStep(), canonicalizeShortcut(), detectModKey() for custom tooling
  • listBindings() — snapshot all active bindings (id, shortcut, trigger, preventDefault, stopPropagation) for palette UIs
  • findShortcutConflicts() — detect prefix/duplicate conflicts before binding a user-customized shortcut
  • Disposable — dispose() + [Symbol.dispose] for using declarations

Documentation

See Also

  • Herald — Typed event bus; pair with Keymap by publishing shortcut events to a bus instead of calling handlers directly
  • Refineore-command-palette uses Keymap internally; register your own shortcuts alongside it
  • Ore — Attach a keymap inside a define() setup function for component-scoped shortcuts