Skip to content
usefy

useHotkeys

Events & gestures

High-level keyboard shortcuts — combos, sequences, the cross-platform <code>mod</code> alias, scoping, and an input-field guard.

Install

$ npm install @usefy/use-hotkeys

Quick start

useHotkeys.tsx
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-platform mod.
  • Key — the final non-modifier token, matched against KeyboardEvent.key case-insensitively, with friendly aliases: escEscape, 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)

OptionTypeDefaultDescription
enabledbooleantrueWhen false, nothing is attached and no hotkey fires; any in-progress sequence buffer is cleared.
targetHTMLElement | RefObject | Document | Window | nulldocumentThe element the listener is bound to. null detaches. Refs are resolved to .current.
eventType"keydown" | "keyup""keydown"Which keyboard event drives matching.
enableOnFormTagsbooleanfalseAllow hotkeys to fire while an editable element is focused. Left off, typing in a form never triggers a shortcut.
preventDefaultbooleanfalseCall event.preventDefault() on a match only. Safe for overriding browser shortcuts such as mod+s.
sequenceTimeoutMsnumber1000How long the sequence buffer waits between combos before resetting.
macbooleanauto-detectOverride mod resolution: truemeta, falsectrl. 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 keydown with event.repeat is 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.

More in events & gestures