A tiny, type-safe, cross-platform keyboard shortcuts library with sensible defaults.
- Case-insensitive:
Ctrl
,CTRL
,ctrl
are the same. - Cross-platform mapping: by default,
ctrl
in 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-shortcuts
import { 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+S
new KeyboardShortcuts(options?: CreateOptions)
register(hotkeys: KeyInput, handler: Handler, options?: AddHandleOptions): string
unregister(id: string): void
updateBinding(id: string, patch: Partial<HandleRecord> & Partial<AddHandleOptions>): void
enable(): void
/disable(): void
enableBinding(id: string): void
/disableBinding(id: string): void
listBindings(): HandleView[]
formatHotkey(hotkey: string): string
clear(): 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:
ctrl
maps to⌘
on macOS,Ctrl
on 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