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 | 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: KeyboardEvent) => {
52      const pressedKey = event.key.toLowerCase();
53
54      // If it's a multi-key combo, check all keys are pressed
55      const comboMatch = combo.every(key => {
56        switch (key) {
57          case "ctrl":
58          case "control":
59            return event.ctrlKey;
60          case "shift":
61            return event.shiftKey;
62          case "alt":
63            return event.altKey;
64          case "meta":
65          case "cmd":
66          case "command":
67            return event.metaKey;
68          default:
69            return key === pressedKey;
70        }
71      });
72
73      if (comboMatch) {
74        if (preventDefault) event.preventDefault();
75        callback(event);
76        if (once) target.removeEventListener(eventType, handler);
77      }
78    };
79
80    target.addEventListener(eventType, handler);
81    return () => {
82      target.removeEventListener(eventType, handler);
83    };
84  }, [keys, callback, eventType, target, once, preventDefault]);
85}
86