javascript 47 lines · 9 steps

Building a keyboard shortcut manager in JavaScript

A class that maps normalized key combos to callbacks and dispatches them on keydown.

Explained by highlit
1class ShortcutManager {
2 constructor(target = window) {
3 this.target = target;
4 this.bindings = new Map();
5 this.handleKeyDown = this.handleKeyDown.bind(this);
6 this.target.addEventListener('keydown', this.handleKeyDown);
7 }
8 
9 normalize(combo) {
10 const parts = combo.toLowerCase().split('+').map((p) => p.trim());
11 const modifiers = { ctrl: false, alt: false, shift: false, meta: false };
12 let key = '';
13 for (const part of parts) {
14 if (part === 'cmd' || part === 'meta') modifiers.meta = true;
15 else if (part === 'ctrl' || part === 'control') modifiers.ctrl = true;
16 else if (part === 'alt' || part === 'option') modifiers.alt = true;
17 else if (part === 'shift') modifiers.shift = true;
18 else key = part;
19 }
20 return `${+modifiers.ctrl}${+modifiers.alt}${+modifiers.shift}${+modifiers.meta}:${key}`;
21 }
22 
23 bind(combo, callback) {
24 const id = this.normalize(combo);
25 if (!this.bindings.has(id)) this.bindings.set(id, new Set());
26 this.bindings.get(id).add(callback);
27 return () => this.unbind(combo, callback);
28 }
29 
30 unbind(combo, callback) {
31 const handlers = this.bindings.get(this.normalize(combo));
32 if (handlers) handlers.delete(callback);
33 }
34 
35 handleKeyDown(event) {
36 const id = `${+event.ctrlKey}${+event.altKey}${+event.shiftKey}${+event.metaKey}:${event.key.toLowerCase()}`;
37 const handlers = this.bindings.get(id);
38 if (!handlers || handlers.size === 0) return;
39 event.preventDefault();
40 for (const handler of handlers) handler(event);
41 }
42 
43 destroy() {
44 this.target.removeEventListener('keydown', this.handleKeyDown);
45 this.bindings.clear();
46 }
47}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Normalizing input into a canonical key lets registration and lookup share one comparable format.
  2. 2Storing callbacks in a Set per combo allows multiple handlers and easy removal without duplicates.
  3. 3Returning an unbind closure from bind gives callers a clean, self-contained way to detach.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a keyboard shortcut manager in JavaScript — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code