| name | use-shortcut |
| description | Build, review, document, or debug React and Next.js keyboard shortcuts with @remcostoeten/use-shortcut. Use when adding shortcut handlers, command palettes, shortcut maps, scoped shortcuts, multi-key sequences, shortcut recording, formatter/parser utilities, accessible debug or attempt UIs, or package/docs/tests for the use-shortcut repository. |
use-shortcut
Use this skill for React keyboard shortcut work with @remcostoeten/use-shortcut. Prefer the narrow React entrypoint for app code:
import { useShortcut } from "@remcostoeten/use-shortcut/react";
Workflow
- Inspect the consumer context before adding shortcuts: target element, active modal/dialog state, focused inputs, required scopes, and expected browser/native shortcut collisions.
- Choose the smallest API that fits:
- Use
useShortcutBinding() for one cleanup-safe shortcut binding in a component.
- Use
useShortcut() for fluent one-off bindings.
- Use
useShortcutMap() or registerShortcutMap() for config-driven command sets.
- Use
.bind(...) when combos already exist as strings from settings or config.
- Use parser/formatter entrypoints only for shortcut UI, persistence, validation, or custom matching.
- Keep shortcuts accessible:
- Do not steal typing from inputs unless explicitly intended; default
ignoreInputs is true.
- Pair invisible or icon-only shortcut affordances with accessible names and visible focus states.
- For debug overlays, toasts, or validation, use polite
aria-live text and non-color status cues.
- Preserve browser zoom, paste, screen-reader navigation, and native form behavior.
- Register cleanup-safe handlers:
- In React components, create fluent
.on(...) registrations inside useEffect() and return cleanup that calls unbind().
- Keep effect dependencies honest; stabilize handlers with
useCallback() or shortcut maps with useMemo() when needed.
- Store returned
ShortcutResults when enabling, disabling, triggering, or unbinding is needed.
- Group many imperative registrations with
createShortcutGroup() or useShortcutGroup().
- Prefer scopes over conditional handler branches when shortcuts belong to app modes.
- Validate behavior with realistic keyboard events and the repo commands relevant to the change.
Common Patterns
Add a declarative shortcut:
useShortcutBinding("mod+k", openCommandPalette, {
description: "Open command palette",
preventDefault: true,
});
Use a sequence:
useShortcutBinding("g then d", goToDashboard, {
description: "Go to dashboard",
sequenceTimeout: 1000,
});
Use scopes:
const $ = useShortcut({ activeScopes: "navigation" });
useEffect(() => {
const shortcut = $.in("editor").mod.key("s").on(saveFile);
$.setScopes("editor");
return () => shortcut.unbind();
}, [$, saveFile]);
Use structured debug metadata:
const $ = useShortcut({
debug: {
console: true,
includeCode: true,
includeLocation: true,
includeKeyCode: true,
},
});
const unsubscribe = $.onDebug((event) => {
showShortcutTelemetry(event.input.combo, event.attempts);
});
Use per-shortcut attempt feedback:
useEffect(() => {
const result = $.shift.key("e").then("e").on(runProbe);
const removeAttempt = result.onAttempt?.((matched, event, details) => {
updateAttemptStatus({ matched, key: event.key, status: details?.status });
});
return () => {
removeAttempt?.();
result.unbind();
};
}, [$, runProbe, updateAttemptStatus]);
API Reference
Read references/api.md when you need exact entrypoints, option names, result shapes, parser/formatter utilities, or testing guidance.
Repository Commands
Use the package scripts from the workspace root:
bun run package:typecheck
bun run package:test
bun run package:build
For docs changes, use:
bun run docs:lint
bun run docs:test
bun run docs:build