React hooks for keyboard shortcuts with runtime editing and key capture.
pnpm add @rdub/use-hotkeysimport { useHotkeys } from '@rdub/use-hotkeys'
const HOTKEYS = {
't': 'setTemp',
'c': 'setCO2',
'ctrl+s': 'save',
'shift+?': 'showHelp',
}
function App() {
useHotkeys(HOTKEYS, {
setTemp: () => setMetric('temp'),
setCO2: () => setMetric('co2'),
save: () => handleSave(),
showHelp: () => setShowHelp(true),
})
}import { useRecordHotkey } from '@rdub/use-hotkeys'
function KeybindingButton() {
const { isRecording, startRecording, display, activeKeys } = useRecordHotkey({
onCapture: (combo, display) => {
console.log(`Captured: ${display.display}`) // "⌘⇧K" on Mac
saveBinding(display.id) // "meta+shift+k"
}
})
return (
<button onClick={() => startRecording()}>
{isRecording
? (activeKeys ? formatCombination(activeKeys).display : 'Press keys...')
: (display?.display ?? 'Click to set')}
</button>
)
}Register keyboard shortcuts.
keymap:Record<string, string | string[]>- maps key combos to action nameshandlers:Record<string, (e: KeyboardEvent) => void>- maps action names to functionsoptions:enabled?: boolean- enable/disable (default: true)target?: HTMLElement | Window- event target (default: window)preventDefault?: boolean- prevent default on match (default: true)stopPropagation?: boolean- stop propagation on match (default: true)enableOnFormTags?: boolean- fire in inputs/textareas (default: false)
Capture key combinations from user input.
options:onCapture?: (combo, display) => void- called when combination capturedonCancel?: () => void- called when recording cancelledpreventDefault?: boolean- prevent default during capture (default: true)
Returns:
isRecording: booleanstartRecording: () => () => void- returns cancel functioncancel: () => voidcombination: KeyCombination | nulldisplay: KeyCombinationDisplay | nullactiveKeys: KeyCombination | null- keys currently held (for live UI feedback)
Wraps useHotkeys with editable keybindings and localStorage persistence.
import { useEditableHotkeys } from '@rdub/use-hotkeys'
const { keymap, setBinding, reset, overrides } = useEditableHotkeys(
{ 't': 'setTemp', 'c': 'setCO2' },
{ setTemp: () => setMetric('temp'), setCO2: () => setMetric('co2') },
{ storageKey: 'app-hotkeys' }
)defaults: Default keymaphandlers: Action handlers (same asuseHotkeys)options:storageKey?: string- localStorage key for persistence (omit to disable)disableConflicts?: boolean- disable keys with multiple actions bound (default: true)- Plus all
useHotkeysoptions
Returns:
keymap: HotkeyMap- current merged keymapsetBinding: (action, key) => void- update a single bindingsetKeymap: (overrides) => void- update multiple bindingsreset: () => void- clear all overridesoverrides: Partial<HotkeyMap>- user overrides onlyconflicts: Map<string, string[]>- keys with multiple actions boundhasConflicts: boolean- whether any conflicts exist
Display keyboard shortcuts in a modal (opens with ? by default).
import { ShortcutsModal } from '@rdub/use-hotkeys'
<ShortcutsModal
keymap={HOTKEYS}
descriptions={{ 'metric:temp': 'Switch to temperature' }}
groups={{ metric: 'Metrics', time: 'Time Range' }}
/>Props:
keymap: HotkeyMap- shortcuts to displaydescriptions?: Record<string, string>- action descriptionsgroups?: Record<string, string>- group prefix → display nameisOpen?: boolean- controlled visibilityonClose?: () => void- close callbackopenKey?: string- key to open (default:'?')autoRegisterOpen?: boolean- auto-register open key (default: true)children?: (props) => ReactNode- custom render function
UI for viewing and editing keybindings with conflict detection.
import { KeybindingEditor } from '@rdub/use-hotkeys'
<KeybindingEditor
keymap={keymap}
defaults={DEFAULT_KEYMAP}
descriptions={{ save: 'Save document' }}
onChange={(action, key) => setBinding(action, key)}
onReset={() => reset()}
/>Props:
keymap: HotkeyMap- current keymapdefaults: HotkeyMap- default keymap (for reset)descriptions?: Record<string, string>- action descriptionsonChange: (action, key) => void- binding change callbackonReset?: () => void- reset callbackchildren?: (props) => ReactNode- custom render function
import {
formatCombination,
parseCombinationId,
findConflicts,
hasConflicts,
} from '@rdub/use-hotkeys'
formatCombination({ key: 'k', modifiers: { meta: true, shift: true, ctrl: false, alt: false }})
// → { display: "⌘⇧K", id: "meta+shift+k" } on Mac
// → { display: "Win+Shift+K", id: "meta+shift+k" } elsewhere
parseCombinationId('ctrl+shift+k')
// → { key: 'k', modifiers: { ctrl: true, shift: true, alt: false, meta: false }}
// Conflict detection
const keymap = { 't': 'setTemp', 't': 'toggleTheme' } // same key!
findConflicts(keymap) // → Map { 't' => ['setTemp', 'toggleTheme'] }
hasConflicts(keymap) // → trueModifier keys: ctrl, alt, shift, meta (or cmd/command on Mac)
Examples:
't'- just T key'shift+t'- Shift+T'ctrl+shift+k'- Ctrl+Shift+K'meta+s'- Cmd+S on Mac, Win+S elsewhere
Projects using @rdub/use-hotkeys:
-
runsascoded/awair – Air quality dashboard with keyboard shortcuts for metric switching
Press
?to see the shortcuts modal, or use single keys to switch metrics:t/c/h/p/v– Temperature / CO₂ / Humidity / PM2.5 / VOC1/3/7/m– 1 day / 3 days / 1 week / 1 month time range
ROADMAP.md - feature overview and future ideas.
MIT