useKeyboard
A React hook to handle keyboard shortcuts and keypress events in a declarative way.
✅ Features
- Easily bind keyboard events globally or to a specific element
- Supports multiple keys and key combinations (e.g., ctrl+s)
- Works in client-side only environments
- Fully typed with TypeScript
📦 Usage
1import { useKeyboard } from './useKeyboard' 2 3useKeyboard('ctrl+s', (e) => { 4 e.preventDefault() 5 console.log('Save triggered!') 6}) 7 8useKeyboard(['ArrowUp', 'ArrowDown'], (e) => { 9 console.log('Arrow key pressed:', e.key) 10})
📋 Source Code
This is the full implementation of useKeyboard
1import { useEffect } from "react";
2
3type KeyCombo = string | string[];
4
5interface UseKeyboardOptions {
6 /**
7 * The key or key combo(s) to listen for.
8 * Example: 'Escape' or ['Control', 'k']
9 */
10 keys: KeyCombo;
11
12 /**
13 * The callback function to run when the key(s) are pressed.
14 */
15 callback: (event: KeyboardEvent) => void;
16
17 /**
18 * Listen on 'keydown' or 'keyup'. Defaults to 'keydown'.
19 */
20 eventType?: "keydown" | "keyup";
21
22 /**
23 * Whether the listener should be global (on window) or scoped to an element.
24 */
25 target?: HTMLElement | Window | null;
26
27 /**
28 * Set to true if you want the event to trigger only once until released.
29 */
30 once?: boolean;
31
32 /**
33 * Optional: Whether to prevent the default action on key press.
34 */
35 preventDefault?: boolean;
36}
37
38export function useKeyboard({
39 keys,
40 callback,
41 eventType = "keydown",
42 target = typeof window !== "undefined" ? window : null,
43 once = false,
44 preventDefault = false,
45}: UseKeyboardOptions) {
46 useEffect(() => {
47 if (!target) return;
48
49 const combo = Array.isArray(keys) ? keys.map(k => k.toLowerCase()) : [keys.toLowerCase()];
50
51 const handler = (event: Event) => {
52 const keyEvent = event as KeyboardEvent;
53 const pressedKey = keyEvent.key.toLowerCase();
54
55 // If it's a multi-key combo, check all keys are pressed
56 const comboMatch = combo.every(key => {
57 switch (key) {
58 case "ctrl":
59 case "control":
60 return keyEvent.ctrlKey;
61 case "shift":
62 return keyEvent.shiftKey;
63 case "alt":
64 return keyEvent.altKey;
65 case "meta":
66 case "cmd":
67 case "command":
68 return keyEvent.metaKey;
69 default:
70 return key === pressedKey;
71 }
72 });
73
74 if (comboMatch) {
75 if (preventDefault) keyEvent.preventDefault();
76 callback(keyEvent);
77 if (once) target.removeEventListener(eventType, handler);
78 }
79 };
80
81 target.addEventListener(eventType, handler);
82 return () => {
83 target.removeEventListener(eventType, handler);
84 };
85 }, [keys, callback, eventType, target, once, preventDefault]);
86}
87