frontend engineering

Build a Hotkeys / Keyboard Shortcuts System

Power users expect keyboard shortcuts. Linear has hundreds; Notion many; Gmail famously does. The interview tests whether you understand the registration patterns, scope handling, and conflict resolution.

Functional requirements

  • Register shortcuts globally and per-component
  • Detect modifier keys (Cmd, Ctrl, Shift, Alt)
  • Handle key combinations (Cmd+Shift+K)
  • Don’t fire when user is typing in an input
  • Show shortcut hints (next to menu items, in command palette)
  • Help dialog showing all shortcuts (?)

Architecture

Two pieces:

  • Global event listener on keydown — one listener attached at the document or window level so shortcuts work no matter what’s focused. Attaching a listener per component instead leads to duplicate handlers and events that get missed depending on focus.
  • Registry of shortcut → handler mappings — a central map keyed by a normalized combo (like cmd+k) that the single listener consults on each keypress. Centralizing it lets you detect duplicate combos, generate the help dialog from one source, and enable or disable groups by scope.

Library: react-hotkeys-hook

The standard React option:

useHotkeys('cmd+k', (e) => {
  e.preventDefault();
  openCommandPalette();
});

Cross-platform: cmd maps to Cmd on Mac, Ctrl on Windows/Linux.

Detecting modifier keys

Cross-platform detection:

  • Mac: e.metaKey for Cmd
  • Windows/Linux: e.ctrlKey for Ctrl
  • Convention: “Cmd/Ctrl” handled by libraries automatically

Don’t fire in inputs

Critical UX: typing “k” in an input should not trigger Cmd+K shortcut’s “k” component.

Check event target:

if (['INPUT', 'TEXTAREA'].includes(e.target.tagName) || e.target.isContentEditable) {
  return; // ignore
}

Exception: shortcuts with modifiers (Cmd+S, Cmd+Z) should fire even in inputs.

Scopes

Different shortcuts in different parts of the app:

  • Global: always active (Cmd+K, Esc) — these fire regardless of focus, so keep the set small and stable to avoid surprising the user.
  • Modal: only when modal is open — bind these when the modal mounts and unbind on close, so Esc closes the modal without also firing a global handler underneath it.
  • Editor: only when editor focused — activate the editor’s shortcut set on focus and tear it down on blur, otherwise formatting keys like Cmd+B leak into the rest of the app.

Scope-aware libraries enable/disable based on focus.

Conflict resolution

Two shortcuts on the same key. Strategies:

  • Most-recently-registered wins — a simple stack where the last handler bound for a key shadows earlier ones; predictable, but a stale registration you forgot to unbind can quietly hide a global shortcut.
  • Most-specific scope wins (modal beats global) — resolve by scope depth so the innermost active context handles the key first, which matches what users expect (Esc closes the open dialog, not the page).
  • Explicit priority numbers — attach a priority to each binding and sort on it; the most flexible option, but you have to keep the numbers coherent as the app grows.

Sequence shortcuts

Some apps support multi-key sequences (Vim-style):

  • “g, i” → go to inbox
  • “g, p” → go to projects

Implementation: track previous key with timeout (~1 second).

Display

Show shortcut hints visibly:

  • Next to menu items: “Edit (E)”
  • In tooltips: “Save (Cmd+S)”
  • In command palette results

Use <kbd> for visual styling.

Help dialog

“?” key (or Cmd+/) opens a dialog listing all shortcuts. Standard UX:

  • Grouped by category (Navigation, Actions, Editing)
  • Each row: shortcut + description
  • Search to filter

Accessibility

  • Don’t conflict with browser shortcuts (don’t override Cmd+T, Cmd+W) — you often can’t reliably preventDefault these anyway, and hijacking them breaks the user’s muscle memory. Interviewers like to hear you name the reserved combos you deliberately avoid.
  • Don’t conflict with screen-reader shortcuts — screen readers reserve many keys (arrows, single letters in browse mode); single-key shortcuts are the worst offenders, so prefer modified combos or scope single keys tightly.
  • Provide alternative interactions (mouse-clickable equivalent) — every shortcut should map to a visible control, so the action stays discoverable and usable without a keyboard.
  • Document all shortcuts — surface them in the help dialog and in tooltips so keyboard-only and assistive-tech users can actually find them.

Common antipatterns

  • Shortcut fires while typing in input — the most common bug; guard on the event target’s tag name and isContentEditable before dispatching.
  • Different platforms have different shortcuts (mismatched Cmd/Ctrl) — hardcoding metaKey breaks Windows users; normalize to a “mod” key that maps to Cmd on Mac and Ctrl elsewhere.
  • No way for users to discover shortcuts — without hints and a help dialog, only power users who read docs benefit; show the combo next to the menu item that triggers it.
  • Conflicting shortcuts (two handlers for same key) — pick a resolution strategy up front instead of relying on registration order by accident.

Frequently Asked Questions

Should I support customizable shortcuts?

For power-user apps (IDEs, design tools): yes. For typical SaaS: defaults are usually enough.

How do I handle international keyboards?

Use e.code (physical key) instead of e.key for layout-independence. Trade-off: French AZERTY users get the same physical-key shortcuts as US QWERTY, even if labels differ.

Can shortcuts work on mobile?

Bluetooth keyboards yes. On-screen keyboards: no — there are no Cmd/Ctrl keys. Provide button alternatives.

newsletter

What's actually being asked right now

Interview patterns & comp trends, straight to your inbox.

No spam. Unsubscribe anytime.

newsletter

What's actually being asked right now

Interview patterns & comp trends, straight to your inbox.

No spam. Unsubscribe anytime.

1972 Soviet postage stamp commemorating the Mars 2 probe

worth a read

Mars For The Rest of Us — a weekly-or-more deep dive on the technical side of Mars exploration: rocket propulsion, microbiology, mission architecture, and everything in between. Written by Maciej Ceglowski.

Read it on Substack →
Scroll to Top