react-hotkeys-hook
react-hotkeys-hook is an ESM React hook for binding keyboard shortcuts to component behavior. useHotkeys accepts combinations, alternatives, or ordered sequences; cleans up DOM listeners with the component lifecycle; supports physical-key and produced-character matching; and can restrict a shortcut by focus, form tags, editable content, a custom Document, or application scopes. A provider exposes active scopes and registered shortcut descriptions, while useRecordHotkeys helps build user-configurable keybinding screens.
react-hotkeys-hook is the sensible React choice when shortcuts belong to component lifecycle and application scopes. Skip it for framework-free code, CommonJS-only tooling, or any product expecting a package to solve shortcut accessibility and conflict design automatically.
Use it if
- You want component-local keyboard shortcuts with automatic listener cleanup and TypeScript declarations
- Your app needs modal, editor, or route scopes so the same key can mean different things in different UI contexts
- You need key sequences, keyup handlers, focus-bound shortcuts, iframe documents, or user-recorded key combinations
- You want one API that can describe registered hotkeys for an in-app shortcut help panel
- Your application is not React: the package has React and ReactDOM peer dependencies and its public API is built around hooks and a context provider; hotkeys-js or tinykeys is the direct fit
- Your build still expects CommonJS: the changelog marks version 5 as ESM-only, so require-based Jest, SSR, or library tooling needs an ESM migration or transpilation layer
- You need modifier-based sequences such as ctrl+k then ctrl+s: the current useHotkeys source contains an explicit TODO that modifiers do not work with sequences
- You expect every browser shortcut to be overrideable: preventDefault defaults to false, and the README specifically notes that protected combinations such as meta+w cannot be overridden
- Keyboard discoverability and accessibility must arrive with the package: it can store a description and expose registered hotkeys, but your app still has to render help, avoid conflicts, provide alternatives, and test screen-reader and keyboard-only flows
Setup reality
npm install react-hotkeys-hook adds no listed runtime dependencies, but React and ReactDOM >=16.8 are peer dependencies and version 5 is ESM-only. Type declarations ship with the package. A simple useHotkeys call needs no provider; HotkeysProvider is required when you actively enable, disable, or toggle named scopes, or when you want the context's list of bound shortcut descriptions. Listeners attach to document by default and clean up with the hook. In Next.js or another React Server Components setup, the component calling the hook must be a client component. Form controls and contenteditable regions are ignored by default, which prevents destructive shortcuts while typing but often surprises search-box and editor implementations; opt in narrowly with enableOnFormTags or enableOnContentEditable. Matching uses physical KeyboardEvent.code by default, so a shortcut follows key position across layouts; useKey switches to the produced character. mod is the portable Command-or-Control modifier. preventDefault must be enabled deliberately and still cannot block protected browser or operating-system shortcuts. The callback receives the native event and parsed hotkey metadata. Named scopes are not magic focus management: activate the correct scope when dialogs and routes change. The focus-trap form returns a callback ref, and its element must be focusable, often with tabIndex. Sequence input uses > and expires after 1000 ms unless configured. For iframe or shadow-boundary work, pass the exact Document and test focus behavior rather than assuming the top-level document sees the event.
Patterns
Bind a component shortcutbind-basic-hotkey
import { useHotkeys } from 'react-hotkeys-hook';
export function SearchButton() {
const [open, setOpen] = useState(false);
useHotkeys('mod+k', () => setOpen(true));
return <button onClick={() => setOpen(true)}>Search</button>;
}mod maps to Control on Windows and Linux and Meta on macOS. The listener is active while the component is mounted.
Prevent the browser default for Saveoverride-save-shortcut
useHotkeys(
'mod+s',
(event) => saveDocument(),
{ preventDefault: true, description: 'Save document' }
);preventDefault defaults to false. Some protected browser and operating-system shortcuts, including meta+w in the README example, cannot be overridden.
Enable a shortcut only in the right stateenable-conditionally
useHotkeys(
'delete',
() => deleteSelectedItem(selectedId),
{ enabled: Boolean(selectedId) },
[selectedId]
);The dependencies array follows useCallback-style rules for values captured by the callback. A function-valued enabled option can inspect each keyboard event instead.
Map several combinations to one callbackbind-multiple-hotkeys
useHotkeys('mod+enter, shift+enter', (event, hotkey) => {
submit({ newWindow: hotkey.shift === true });
});Comma is the default delimiter between alternatives and plus joins keys inside one combination. Both characters are configurable when they are literal keys.
Enable a shortcut in selected form fieldsallow-form-shortcut
useHotkeys(
'escape',
() => clearSearch(),
{ enableOnFormTags: ['input', 'searchbox'] }
);Form controls are ignored by default. The list accepts input, textarea, select, and documented ARIA roles; contenteditable needs enableOnContentEditable separately.
Activate shortcuts by application scopecontrol-hotkey-scopes
import { HotkeysProvider, useHotkeys, useHotkeysContext } from 'react-hotkeys-hook';
function Editor() {
const { toggleScope } = useHotkeysContext();
useHotkeys('mod+k', openCommandMenu, { scopes: ['editor'] });
return <button onClick={() => toggleScope('editor')}>Toggle editor keys</button>;
}
export function App() {
return <HotkeysProvider initiallyActiveScopes={['editor']}><Editor /></HotkeysProvider>;
}Scope controls require HotkeysProvider. Enabling a named scope while the wildcard scope is active replaces the wildcard rather than adding beside it.
Limit a shortcut to a focused regionbind-to-focused-region
const ref = useHotkeys<HTMLDivElement>('mod+a', selectAllCards);
return (
<div ref={ref} tabIndex={0} aria-label="Card grid">
{cards.map(renderCard)}
</div>
);The returned callback ref binds to that element and its focused descendants. The region must be focusable, and focus behavior should be visible and tested.
Trigger an ordered key sequencebind-key-sequence
useHotkeys('g>g', () => scrollToTop(), {
sequenceTimeoutMs: 750,
description: 'Go to top',
});> is the default sequence separator and the default timeout is 1000 ms. Current source explicitly does not support modifier keys inside sequences.
Run behavior on key releasehandle-key-release
useHotkeys(
'space',
() => setPanning(false),
{ keydown: false, keyup: true }
);keydown defaults to true and keyup to false. Setting both true invokes the callback on both event phases, subject to the hook's repeat protection.
Match the character produced by the keyboard layoutmatch-produced-character
useHotkeys('z', undo, { useKey: true });The default matches physical KeyboardEvent.code. useKey matches KeyboardEvent.key, which follows the active layout and can differ with Shift or input method behavior.
Listen inside an iframe documentbind-iframe-document
const frameDocument = iframeRef.current?.contentDocument;
useHotkeys('mod+b', toggleBold, {
document: frameDocument,
enableOnContentEditable: true,
});The iframe must be same-origin to access contentDocument. The option needs the actual Document object, and it may be undefined until the frame loads.
Record a user-selected shortcutrecord-user-hotkey
import { useRecordHotkeys } from 'react-hotkeys-hook';
const [keys, { start, stop, resetKeys, isRecording }] = useRecordHotkeys(false, ['escape']);
return (
<>
<button onClick={isRecording ? stop : start}>
{isRecording ? 'Finish' : 'Record shortcut'}
</button>
<button onClick={resetKeys}>Clear</button>
<output>{[...keys].join('+')}</output>
</>
);Recording prevents default behavior for captured keys and uses document keydown events. Blacklisted keys keep their normal behavior and are not added.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| hotkeys-js | npm | You need a framework-neutral imperative shortcut library and will manage binding cleanup yourself |
| tinykeys | npm | You want a very small DOM keyboard-combination listener and do not need React scopes or recording hooks |
| @mantine/hooks | npm | Your app already uses Mantine and its useHotkeys hook covers the simpler shortcut behavior you need |