@remcostoeten/use-shortcut
v2.2.0
Published
Tiny, chainable React keyboard shortcuts with sequences, scopes, and typed debug hooks.
Maintainers
Readme
@remcostoeten/use-shortcut
Tiny, chainable keyboard shortcuts for React and Next.js.
The package keeps the fluent useShortcut() API, but it is now documented as explicit entrypoints so consumers can choose the narrowest surface that fits their use case.
Entrypoints
@remcostoeten/use-shortcutFull compatibility barrel.@remcostoeten/use-shortcut/reactRecommended React entrypoint.@remcostoeten/use-shortcut/parserParser and matcher utilities.@remcostoeten/use-shortcut/formatterDisplay formatting utilities such asformatShortcut()andgetModifierSymbols().@remcostoeten/use-shortcut/constantsPlatform and normalization constants.
This package is for React and Next.js apps. If you are building on that stack, prefer @remcostoeten/use-shortcut/react.
Size
Measured in this package on March 12, 2026:
- root published ESM build: about
16.5 kBminified - app bundle for
useShortcutonly: about13.8 kBminified - gzip for the React hook path: about
5.3 kB
That means the runtime is already small in practice. The entrypoint split mainly prevents accidental convenience-barrel imports and makes the architecture explicit.
React API
The public runtime API is React-only. Parser, formatter, and constants exports are supporting utilities inside the same React/Next.js package, not a separate framework-agnostic runtime.
import { useShortcut } from "@remcostoeten/use-shortcut/react"
function App() {
const $ = useShortcut()
$.mod.key("k").on(() => openPalette(), { preventDefault: true })
$.key("escape").on(() => closePalette())
return <div>Press Cmd/Ctrl+K</div>
}Main React exports:
useShortcut(options?)useShortcutMap(shortcutMap, options?)registerShortcutMap(builder, shortcutMap)createShortcutGroup()useShortcutGroup()
Features
- Chainable shortcut builder:
$.mod.key("k").on(handler) - Bulk shortcut maps:
useShortcutMap()andregisterShortcutMap() - Modifier support:
ctrl,shift,alt,cmd,mod - Sequence support:
$.key("g").then("d") - Scope-aware shortcuts with
.in(...),setScopes,enableScope,disableScope - Exception predicates and presets with
.except(...) - Recording mode with
$.record({ timeoutMs }) - Structured debug stream with
$.onDebug(...) - Per-shortcut attempt inspection with
result.onAttempt(...) - Conflict detection for exact and sequence-prefix overlaps
- Priority ordering and
stopOnMatch - Global guard/filter support via
eventFilter
Shortcut Map Example
import { useShortcutMap } from "@remcostoeten/use-shortcut/react"
function App() {
useShortcutMap(
{
openPalette: {
keys: "mod+k",
handler: () => openPalette(),
options: { preventDefault: true },
},
closePalette: {
keys: "escape",
handler: () => closePalette(),
},
toggleSidebar: {
keys: "g then s",
handler: () => toggleSidebar(),
},
},
{ ignoreInputs: false },
)
return <div>Shortcuts ready</div>
}Debug Example
import { useShortcut } from "@remcostoeten/use-shortcut/react"
const $ = useShortcut({
debug: {
console: true,
includeCode: true,
includeLocation: true,
includeKeyCode: true,
},
})
const removeDebug = $.onDebug((event) => {
console.log("key", event.input.combo, event.attempts)
})
const result = $.shift.key("e").then("e").on(runProbe, {
description: "sequence probe",
})
const removeAttempt = result.onAttempt?.((matched, _event, details) => {
console.log(matched ? "matched" : details?.status, details?.steps)
})Architecture
src/builder.tsChainable builder runtime and registration plumbing.src/runtime/*Listener attachment, matching, conflicts, guards, recording, and debug internals.src/hook.tsReact integration and bulk registration helpers.src/react.tsNarrow React entrypoint for hook consumers.src/parser.ts,src/formatter.ts,src/constants.tsStandalone utility entrypoints.src/index.tsFull compatibility barrel.
The API design keeps the fluent React path front-and-center while still exposing low-level parser and formatter utilities when needed.
Development
bun run typecheck
bun run test
bun run buildLicense
MIT
