TanStack Hotkeys provides the hook for building UIs where users record multi-chord sequences (Vim-style shortcuts). Each step is captured like a single hotkey chord; users finish with Enter by default, or you can use manual commit and optional idle timeout.
import { useHotkeySequenceRecorder, formatForDisplay } from '@tanstack/react-hotkeys'
import type { HotkeySequence } from '@tanstack/react-hotkeys'
function HotkeySequenceRecorder() {
const recorder = useHotkeySequenceRecorder({
onRecord: (sequence: HotkeySequence) => {
console.log('Recorded:', sequence)
},
})
return (
<div>
<button
type="button"
onClick={
recorder.isRecording ? recorder.cancelRecording : recorder.startRecording
}
>
{recorder.isRecording
? 'Press chords, then Enter…'
: recorder.recordedSequence
? recorder.recordedSequence.map((h) => formatForDisplay(h)).join(' ')
: 'Click to record'}
</button>
{recorder.isRecording && (
<button type="button" onClick={recorder.cancelRecording}>
Cancel
</button>
)}
</div>
)
}import { useHotkeySequenceRecorder, formatForDisplay } from '@tanstack/react-hotkeys'
import type { HotkeySequence } from '@tanstack/react-hotkeys'
function HotkeySequenceRecorder() {
const recorder = useHotkeySequenceRecorder({
onRecord: (sequence: HotkeySequence) => {
console.log('Recorded:', sequence)
},
})
return (
<div>
<button
type="button"
onClick={
recorder.isRecording ? recorder.cancelRecording : recorder.startRecording
}
>
{recorder.isRecording
? 'Press chords, then Enter…'
: recorder.recordedSequence
? recorder.recordedSequence.map((h) => formatForDisplay(h)).join(' ')
: 'Click to record'}
</button>
{recorder.isRecording && (
<button type="button" onClick={recorder.cancelRecording}>
Cancel
</button>
)}
</div>
)
}| Property | Type | Description |
|---|---|---|
| Whether the recorder is listening | ||
| Chords captured in the current session | ||
| Last committed sequence | ||
| Start a new session | ||
| Stop without calling | ||
| Stop and call | ||
| Commit current (no-op if empty) |
Core options live on from :
<HotkeysProvider
defaultOptions={{
hotkeySequenceRecorder: {
idleTimeoutMs: 2000,
},
}}
>
<App />
</HotkeysProvider><HotkeysProvider
defaultOptions={{
hotkeySequenceRecorder: {
idleTimeoutMs: 2000,
},
}}
>
<App />
</HotkeysProvider>The supports an option (defaults to ). When , the recorder will not intercept normal typing in text inputs, textareas, selects, or contentEditable elements -- keystrokes pass through to the input as usual. Pressing Escape still cancels recording even when focused on an input. Set if you want the recorder to capture keys from within input elements.
| Input | Behavior |
|---|---|
| Valid chord | Appended to ; listener stays active |
| Enter (no modifiers), , | Commits and calls |
| Escape | Cancels; |
| Backspace / Delete (no modifiers) | Removes last step, or if empty runs + and stops |
Recorded chords use portable format, same as .
wraps the class and subscribes to its TanStack Store, same pattern as .