useKeyBinding registers a keydown listener and runs your callback when the
requested key combination matches.
When to use
- Add shortcuts like
Meta + kfor command palette toggles. - Trigger actions from keyboard-only workflows.
- Scope shortcuts to a specific element instead of the whole document.
Supported keys
- Modifier keys:
Meta,Ctrl/Control,Alt,Shift. - Regular keys: any
KeyboardEvent.keyvalue (for exampleEnter,k,Escape). - Combinations: pass modifiers and regular keys together.
Example
useKeyBinding(['Meta', 'k'], event => {
event.preventDefault();
openCommandPalette();
});
const containerRef = useRef<HTMLDivElement>(null);
useKeyBinding(
['Enter'],
() => {
submitSelection();
},
containerRef.current
);
Usage
import { useState } from 'react';
import { useKeyBinding } from '@branditdev/hooks';
export function UseKeyBindingDemo() {
const [count, setCount] = useState(0);
useKeyBinding(['Meta', 'k'], () => {
setCount(current => current + 1);
});
return (
<div>
<p>Press Cmd+K (or Windows key+K) to trigger the shortcut.</p>
<p>Shortcut triggered: {count}</p>
</div>
);
}
API
useKeyBinding(
keys,callback,node?):void
Defined in: effects/useKeyBinding/useKeyBinding.ts:28
Registers a keyboard shortcut and executes a callback on match.
The hook supports modifier keys (Meta, Ctrl/Control, Alt, Shift),
regular keys, and combinations of both.
Parameters
| Name | Type | Optional | Default | Description |
|---|---|---|---|---|
| keys | string[] | no | - | Key definition for the shortcut. Example: ['Meta', 'k']. |
| callback | (event) => void | no | - | Handler invoked when the key combination matches. |
| node | Node | null | yes | - | Optional target node to attach the listener to. Defaults to document. |
Returns
void
Source
import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
const MODIFIER_KEYS = {
Meta: 'metaKey',
Ctrl: 'ctrlKey',
Control: 'ctrlKey',
Alt: 'altKey',
Shift: 'shiftKey',
} as const;
type ModifierKey = keyof typeof MODIFIER_KEYS;
function isModifierKey(key: string): key is ModifierKey {
return key in MODIFIER_KEYS;
}
export function useKeyBinding(
keys: string[],
callback: (event: KeyboardEvent) => void,
node: Node | null = null
) {
// implement the callback ref pattern
const callbackRef = useRef(callback);
useLayoutEffect(() => {
callbackRef.current = callback;
});
// handle what happens on key press
const handleKeyPress = useCallback(
(event: Event) => {
if (!(event instanceof KeyboardEvent)) {
return;
}
const e = event;
// Separate modifier keys from regular keys
const modifierKeys = keys.filter(isModifierKey);
const regularKeys = keys.filter(key => !isModifierKey(key));
// Check if all required modifier keys are pressed
const modifiersPressed = modifierKeys.every(modifier => {
const modifierProperty = MODIFIER_KEYS[modifier];
return e[modifierProperty];
});
// Check if one of the regular keys matches (or if no regular keys specified, just check modifiers)
const keyMatches =
regularKeys.length === 0 || regularKeys.some(key => e.key === key);
// Execute callback if all conditions are met
if (
modifiersPressed &&
keyMatches &&
(modifierKeys.length > 0 || regularKeys.length > 0)
) {
callbackRef.current(e);
}
},
[keys]
);
useEffect(() => {
// target is either the provided node or the document
const targetNode = node ?? document;
// attach the event listener
if (targetNode) targetNode.addEventListener('keydown', handleKeyPress);
// remove the event listener
return () =>
targetNode && targetNode.removeEventListener('keydown', handleKeyPress);
}, [handleKeyPress, node]);
}