React keyboard shortcuts: the useKeybind hook

A component-scoped shortcut in one line — registered on mount, cleaned up on unmount, always calling your latest callback.

Install

react is a peer dependency of the separate bind-keyboard/react entry point — the core bind-keyboard import is completely unaffected either way, so adding this never pulls React into a non-React project.

terminal
npm install bind-keyboard

Basic usage

useKeybind(keyCombination, callback, options?) creates its own BindKeyboard instance inside a useEffect and destroys it on cleanup — the component never leaks a listener past its own lifetime.

SaveButton.tsx
import { useKeybind } from "bind-keyboard/react";

function SaveButton({ onSave }: { onSave: () => void }) {
  useKeybind("cmdOrCtrl+s", (event) => {
    event.preventDefault(); // stop the browser's own "Save page" dialog
    onSave();
  });

  return <button onClick={onSave}>Save</button>;
}

No stale closures, no unnecessary re-subscribing

callback doesn't need to be stable across renders — the latest one is always used (via a ref internally), without tearing down and recreating the underlying binding just because an inline function changed identity on every render. keyCombination and the rest of options are compared by their serialized content, not by reference, so passing a literal array or object inline on every render (useKeybind(["ctrl+a", "ctrl+b"], cb)) never needlessly resubscribes either — only an actual change to what they contain does.

Only while the component wants it: enabled

Pass enabled: false to unregister without unmounting — useful for a shortcut that should only be live under some condition, e.g. only while a specific tab is active:

Tab.tsx
useKeybind("cmdOrCtrl+f", openSearch, {
  enabled: isActiveTab,
});

Everything .add() accepts, plus a few more

options accepts everything the core .add() method's own options object does — allowInInputElements, override, description, scope — plus preventRepeat/type (mirroring .add()'s own positional arguments), enabled, and the constructor options that matter per binding (target, keyMode, checkInputElements, debug). Each useKeybind() call owns an independent BindKeyboard instance, so there's no shared, app-wide instance to configure elsewhere — see scoping shortcuts inside a modal for a pattern built entirely out of the scope option.

Why this is SSR-safe automatically

Registration only ever happens inside useEffect, which React never runs during server rendering — so the DOM-touching BindKeyboard instance underneath simply never gets created on the server at all. There's no special SSR mode to opt into; it falls out of how the hook is built. See keyboard shortcuts in Next.js for the framework-specific details (App Router's "use client", Pages Router).