A tiny, type-safe, cross-platform keyboard shortcuts library with sensible defaults.
- Case-insensitive:
Ctrl,CTRL,ctrlare the same. - Cross-platform mapping: by default,
ctrlin hotkey strings is treated as Command (⌘) on macOS (event side remains real keys). - Zero dependencies, tree-shakable, ESM/CJS, full TypeScript types.
npm install kbd-shortcuts
# or
pnpm add kbd-shortcuts
# or
yarn add kbd-shortcutsimport { KeyboardShortcuts } from 'kbd-shortcuts';
const kbd = new KeyboardShortcuts({
// enabled: true,
// eventType: 'keydown',
// target: window,
// ctrlMapsToCommandOnMac: true,
});
// Register a binding (macOS: ⌘S, Windows/Linux: Ctrl+S)
const id = kbd.register(
'ctrl+s',
({ platform }) => {
console.log('Save triggered on', platform);
},
{
preventDefault: true,
description: 'Save',
}
);
// Another example
kbd.register('ctrl+shift+p', () => console.log('Command Palette'));
// Controls
kbd.disable();
kbd.enable();
// Single binding controls
kbd.disableBinding(id);
kbd.enableBinding(id);
// Update binding
kbd.updateBinding(id, { once: true, description: 'Save (once)' });
// List for UI
console.log(kbd.listBindings());
// Display-friendly string
console.log(kbd.formatHotkey('ctrl+s')); // mac: ⌘S, win: Ctrl+Snew KeyboardShortcuts(options?: CreateOptions)register(hotkeys: KeyInput, handler: Handler, options?: AddHandleOptions): stringunregister(id: string): voidupdateBinding(id: string, patch: Partial<HandleRecord> & Partial<AddHandleOptions>): voidenable(): void/disable(): voidenableBinding(id: string): void/disableBinding(id: string): voidlistBindings(): HandleView[]formatHotkey(hotkey: string): stringclear(): void
Note: First-match-wins. Bindings are sorted by priority (higher first). Once a binding is matched, processing stops.
| Name | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Global enable switch |
eventType |
'keydown' | 'keyup' |
'keydown' |
Event type to listen |
target |
Window | Document | HTMLElement |
window |
Event target |
ctrlMapsToCommandOnMac |
boolean |
true |
When parsing hotkey strings on macOS, map ctrl to Command(⌘) |
| Name | Type | Default | Description |
|---|---|---|---|
preventDefault |
boolean |
true |
Call event.preventDefault() on match |
stopPropagation |
boolean |
true |
Call event.stopPropagation() on match |
allowInEditable |
boolean |
false |
Allow triggers in inputs/contenteditable |
enabled |
boolean |
true |
Binding is active |
once |
boolean |
false |
Trigger once then disable binding |
priority |
number |
0 |
Higher first; first match wins |
ctrlMapsToCommandOnMac |
boolean |
inherit global | Override global mapping for this binding |
description |
string |
'' |
For UI display |
- KeyInput:
string | string[], e.g.,"ctrl+s"or["ctrl+s", "cmd+s"] - Syntax:
modifier+...+key - Modifiers:
ctrl,meta,cmd,command,win,super,alt,option,opt,shift - Case insensitive:
Ctrl+S===ctrl+s - Symbol support:
⌘,⌥,⇧,⌃are recognized
Use * or any as the main key to match any key:
kbd.register('shift+*', () => {
console.log('Shift + any key pressed');
});- Hotkey strings:
ctrlmaps to⌘on macOS,Ctrlon Windows/Linux - Event matching: Uses actual pressed keys (strict matching)
- Display:
formatHotkey()shows platform-appropriate symbols
- Bindings are sorted by priority (descending)
- First match wins and stops processing
- Higher priority = processed first
const kbd = new KeyboardShortcuts();
// Save shortcut
kbd.register('ctrl+s', () => {
console.log('Save!');
});
// Copy with multiple variants
kbd.register(['ctrl+c', 'ctrl+insert'], () => {
console.log('Copy!');
});const kbd = new KeyboardShortcuts({
target: document.querySelector('.editor'),
ctrlMapsToCommandOnMac: false, // Use real Ctrl on Mac
});
// High priority shortcut
kbd.register(
'escape',
() => {
console.log('Escape pressed');
},
{ priority: 100 }
);
// Allow in input fields
kbd.register(
'ctrl+enter',
() => {
console.log('Submit form');
},
{ allowInEditable: true }
);
// One-time shortcut
kbd.register(
'ctrl+shift+d',
() => {
console.log('Debug mode activated');
},
{ once: true }
);import { useEffect, useRef } from 'react';
import { KeyboardShortcuts } from 'kbd-shortcuts';
function App() {
const kbdRef = useRef<KeyboardShortcuts>();
useEffect(() => {
kbdRef.current = new KeyboardShortcuts();
const saveId = kbdRef.current.register('ctrl+s', () => {
console.log('Save triggered');
});
return () => {
kbdRef.current?.destroy();
};
}, []);
return <div>My App</div>;
}- If you need the real Control key on macOS, set
ctrlMapsToCommandOnMac: false - Processing stops at first match to avoid ambiguity
- Bindings in editable elements are blocked by default for better UX
- All hotkey strings are normalized (case-insensitive, whitespace-trimmed)
MIT