112
LVL 04 — SENIOR-IN-TRAININGSESSION 112DAY 112

KEYBOARD SHORTCUTS

🎫 PIXELCRAFT-098
Feature | 🟡 Medium | Priority: 🟡 Medium

Power users demand keyboard shortcuts. Currently only Ctrl+Z/Y work. Build a comprehensive shortcut system: customizable bindings, conflict detection, cross-platform Cmd/Ctrl, and a cheat sheet overlay triggered by pressing ?
CONCEPTS.UNLOCKED
⌨️
Shortcut Manager
Central registry for all keyboard shortcuts. Map key combinations to actions. Handle registration, deregistration, and execution. One system, consistent behavior across the entire app.
⚠️
Conflict Detection
Two features can't share the same shortcut. When registering a new shortcut, check if it's already taken. When customizing, warn about conflicts. Browser defaults (Ctrl+S, Ctrl+P) need special handling.
🔧
Customizable Bindings
Users remap shortcuts to their preference. Store custom mappings in Zustand + persist. Default bindings work out of the box. "Record new shortcut" dialog captures the next key combination.
Cheat Sheet Overlay
Press ? to see all shortcuts. Grouped by category (File, Edit, View, Tools). Shows current bindings (including custom). Dismiss with Escape. Every professional app has this.
🖥️
Cross-Platform Modifiers
Cmd on Mac, Ctrl on Windows/Linux. Use e.metaKey || e.ctrlKey for the "primary modifier." Display ⌘ or Ctrl based on navigator.platform. One codebase, correct labels everywhere.
HANDS-ON.TASKS
01
Shortcut Manager
// lib/shortcuts.ts interface Shortcut { id: string; label: string; category: 'file' | 'edit' | 'view' | 'tools' | 'filters'; defaultKey: string; // e.g. "mod+s", "shift+r", "[" action: () => void; } const isMac = navigator.platform .includes('Mac'); function parseCombo(combo: string) { const parts = combo.toLowerCase() .split('+'); return { mod: parts.includes('mod'), shift: parts.includes('shift'), alt: parts.includes('alt'), key: parts[parts.length - 1], }; } function matchesEvent( combo: string, e: KeyboardEvent ): boolean { const c = parseCombo(combo); const modPressed = isMac ? e.metaKey : e.ctrlKey; return ( c.mod === modPressed && c.shift === e.shiftKey && c.alt === e.altKey && c.key === e.key.toLowerCase() ); }
02
Register Default Shortcuts
// lib/defaultShortcuts.ts const defaults: Shortcut[] = [ // File { id: 'save', label: 'Save', category: 'file', defaultKey: 'mod+s', action: () => saveProject() }, { id: 'export', label: 'Export', category: 'file', defaultKey: 'mod+shift+e', action: () => openExportDialog() }, { id: 'open', label: 'Open', category: 'file', defaultKey: 'mod+o', action: () => openFile() }, // Edit { id: 'undo', label: 'Undo', category: 'edit', defaultKey: 'mod+z', action: () => undo() }, { id: 'redo', label: 'Redo', category: 'edit', defaultKey: 'mod+shift+z', action: () => redo() }, { id: 'copy', label: 'Copy', category: 'edit', defaultKey: 'mod+c', action: () => copySelection() }, // Tools { id: 'select', label: 'Select', category: 'tools', defaultKey: 'v', action: () => setTool('select') }, { id: 'crop', label: 'Crop', category: 'tools', defaultKey: 'c', action: () => setTool('crop') }, { id: 'eyedropper', label: 'Eyedropper', category: 'tools', defaultKey: 'i', action: () => setTool('eyedropper') }, // View { id: 'zoomIn', label: 'Zoom In', category: 'view', defaultKey: 'mod+=', action: () => zoomIn() }, { id: 'zoomOut', label: 'Zoom Out', category: 'view', defaultKey: 'mod+-', action: () => zoomOut() }, { id: 'fitScreen', label: 'Fit to Screen', category: 'view', defaultKey: 'mod+0', action: () => fitToScreen() }, ];
03
Global Keyboard Listener
// hooks/useShortcuts.ts function useShortcuts( shortcuts: Shortcut[] ) { const customBindings = usePrefsStore( s => s.customShortcuts); useEffect(() => { function handleKeyDown( e: KeyboardEvent ) { // Don't trigger in input fields const tag = (e.target as HTMLElement) .tagName; if (tag === 'INPUT' || tag === 'TEXTAREA') return; for (const shortcut of shortcuts) { const key = customBindings[shortcut.id] ?? shortcut.defaultKey; if (matchesEvent(key, e)) { e.preventDefault(); shortcut.action(); return; } } } window.addEventListener( 'keydown', handleKeyDown); return () => window.removeEventListener( 'keydown', handleKeyDown); }, [shortcuts, customBindings]); }
04
Cheat Sheet Overlay
function ShortcutOverlay({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) { if (!isOpen) return null; const grouped = groupBy(shortcuts, 'category'); const labels: Record<string,string> = { file: 'File', edit: 'Edit', view: 'View', tools: 'Tools', filters: 'Filters', }; return ( <div className="shortcut-overlay" onClick={onClose}> <div className="shortcut-grid"> {Object.entries(grouped) .map(([cat, items]) => ( <div key={cat}> <h3>{labels[cat]}</h3> {items.map(s => ( <div key={s.id} className="shortcut-row"> <span>{s.label}</span> <kbd> {formatKey(s.defaultKey)} </kbd> </div> ))} </div> ))} </div> </div> ); } function formatKey(combo: string) { return combo .replace('mod', isMac ? '⌘' : 'Ctrl') .replace('shift', '⇧') .replace('alt', isMac ? '⌥' : 'Alt') .replace('+', ' + ') .toUpperCase(); // "mod+shift+e" → "⌘ + ⇧ + E" }
05
Close the Ticket
git switch -c feature/PIXELCRAFT-098-shortcuts git add src/lib/shortcuts.ts src/hooks/ src/components/ git commit -m "Add keyboard shortcut system + cheat sheet (PIXELCRAFT-098)" git push origin feature/PIXELCRAFT-098-shortcuts # PR → Review → Merge → Close ticket ✅
CS.DEEP-DIVE

Keyboard shortcuts are a study in human-computer interaction.

Good shortcuts follow Fitts's Law inverted: the fastest interaction has zero distance — your fingers are already on the keyboard.

// Interaction speed hierarchy:

Keyboard shortcut  ~200ms
  (fingers already there)
Context menu       ~500ms
  (right-click + find item)
Toolbar button      ~800ms
  (move mouse + click)
Menu navigation    ~1200ms
  (menu + submenu + click)

// Design principles:
Mnemonic: V=moVe, C=Crop, I=pIck
Consistent: Mod+Z always undoes
Discoverable: shown in tooltips
Progressive: simple tools = 1 key,
  complex actions = modifier combos

// Same conventions since 1984:
// Cmd+C, Cmd+V, Cmd+Z (Mac Lisa)
// 40 years of muscle memory.
"Shortcuts Lab"
[A]Add shortcut customization: Settings → Keyboard Shortcuts → click a shortcut → "Press new key combination" → captures the next keydown → saves to Zustand persist. Include a "Reset to Defaults" button.
[B]Add conflict detection: when recording a new shortcut, if it conflicts with an existing one, show a warning: "Ctrl+S is already assigned to Save. Replace?" Prevent duplicate bindings.
[C]Research: how do VS Code, Figma, and Photoshop handle keyboard shortcuts? VS Code uses a keybindings.json with "when" clauses for context-dependent shortcuts. How would you implement context-aware shortcuts in PixelCraft?
REF.MATERIAL
ARTICLE
MDN Web Docs
Official keyboard event reference: key, code, metaKey, ctrlKey, shiftKey, altKey. Understanding the difference between key and code.
KEYBOARDOFFICIALESSENTIAL
ARTICLE
Nielsen Norman Group
UX research on keyboard shortcuts: discoverability, learnability, and design guidelines. When shortcuts help and when they confuse.
UXRESEARCH
VIDEO
Visual Studio Code
How VS Code implements keyboard shortcuts: keybindings.json, "when" context clauses, and the key recording UI. The gold standard for shortcut customization.
VSCODEREFERENCE
ARTICLE
Jamiebuilds
Tiny keyboard shortcut library (~400B): key sequences, modifier keys, and cross-platform support. Minimal alternative to building your own.
LIBRARYMINIMAL
ARTICLE
Wikipedia
The foundational HCI law: time to reach a target depends on distance and size. Explains why keyboard shortcuts are faster than mouse interactions.
HCITHEORY
// LEAVE EXCITED BECAUSE
Press ? → see every shortcut. V for select, C for crop, Mod+Z for undo. Cross-platform: ⌘ on Mac, Ctrl on Windows. Customizable bindings. This is what makes an app feel professional.