useHotkeys
Events & gesturesHigh-level keyboard shortcuts — combos, sequences, the cross-platform <code>mod</code> alias, scoping, and an input-field guard.
Install
$ npm install @usefy/use-hotkeysQuick start
import { useHotkeys } from "@usefy/use-hotkeys";
import { useState } from "react";
function App() {
const [open, setOpen] = useState(false);
// Cmd/Ctrl + K toggles a command palette (browser default suppressed)
useHotkeys("mod+k", () => setOpen((o) => !o), { preventDefault: true });
// Multiple bindings for one handler
useHotkeys(["mod+s", "ctrl+enter"], save, { preventDefault: true });
// A sequence: press "g" then "i"
useHotkeys("g i", () => navigate("/inbox"));
return open ? <CommandPalette /> : null;
}API reference
useHotkeys(
keys: string | string[],
handler: (event: KeyboardEvent, match: HotkeyMatch) => void,
options?: UseHotkeysOptions
): void;
keys
A hotkey string, or an array of them (all bound to the same handler, matched as OR). A hotkey is either a single combo ("mod+k") or a space-separated sequence of combos ("g i").
- Modifier tokens (case-insensitive):
ctrl/control,shift,alt/option,meta/cmd/command, and the cross-platformmod. - Key — the final non-modifier token, matched against
KeyboardEvent.keycase-insensitively, with friendly aliases:esc→Escape,space→" ",up/down/left/right→arrow keys,enter/return,del/delete,tab,backspace,plus/+. - Because whitespace separates a sequence, write the space bar as
"space"(not a literal" ").
handler
(event: KeyboardEvent, match: HotkeyMatch) => void. The match object reports { hotkey, index, sequence } — the raw string that matched, its index in the keys array, and whether it was a multi-combo sequence. The handler is kept in a ref, so you never need to memoize it and the listener is not re-subscribed when it changes.
options (UseHotkeysOptions)
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | When false, nothing is attached and no hotkey fires; any in-progress sequence buffer is cleared. |
target | HTMLElement | RefObject | Document | Window | null | document | The element the listener is bound to. null detaches. Refs are resolved to .current. |
eventType | "keydown" | "keyup" | "keydown" | Which keyboard event drives matching. |
enableOnFormTags | boolean | false | Allow hotkeys to fire while an editable element is focused. Left off, typing in a form never triggers a shortcut. |
preventDefault | boolean | false | Call event.preventDefault() on a match only. Safe for overriding browser shortcuts such as mod+s. |
sequenceTimeoutMs | number | 1000 | How long the sequence buffer waits between combos before resetting. |
mac | boolean | auto-detect | Override mod resolution: true → meta, false → ctrl. Leave undefined to detect from the UA. |
Exported helpers & types
parseHotkey(hotkey, { mac? }), isMacPlatform(), isHotkeysSupported(), and the types Hotkey, HotkeyTarget, HotkeyHandler, HotkeyMatch, ParsedChord, ParsedHotkey, UseHotkeysOptions.
Notes on behavior
- Auto-repeat is ignored — holding a combo fires once (a held
keydownwithevent.repeatis skipped), which also keeps sequence buffers clean. - Lone modifier presses (Ctrl/Shift/Alt/Meta by themselves) neither start a combo nor break an in-progress sequence.
- Letter case in the key token is ignored — write
"shift+a"rather than"A"when you need the Shift modifier.
Go deeper
This page is the quick reference. For every example, prop, and edge case, read the full README — or open Storybook to change props live.