Docs
Cloudflare
Railway
CodeRabbit
OpenRouter
SerpAPI
AG Grid
Netlify
WorkOS
Clerk
Prisma
Unkey
Electric
Sentry
Cloudflare
Railway
CodeRabbit
OpenRouter
SerpAPI
AG Grid
Netlify
WorkOS
Clerk
Prisma
Unkey
Electric
Sentry
API Reference
Hotkeys API Reference
Hotkey Sequence API Reference
Key hold & held keys API Reference
Hotkey Recorder API Reference
Hotkey Sequence Recorder API Reference
Normalization & format API Reference
Guides

Sequence Recording Guide

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.

Basic usage

tsx
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>
  )
}

Return value

PropertyTypeDescription
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)

Options

Core options live on from :

  • — called when a sequence is committed (including when cleared via Backspace with no steps).
  • , — same intent as the hotkey recorder.
  • (default) or . With , only (or ) finishes recording; plain Enter can be recorded as a chord.
  • — when is , set to to treat Enter as a normal chord (then use or idle timeout to finish).
  • — optional milliseconds of inactivity after the last completed chord to auto-commit. The timer does not run while waiting for the first chord.

Provider defaults

tsx
<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.

Behavior

InputBehavior
Valid chordAppended to ; listener stays active
Enter (no modifiers), , Commits and calls
EscapeCancels;
Backspace / Delete (no modifiers)Removes last step, or if empty runs + and stops

Recorded chords use portable format, same as .

Under the hood

wraps the class and subscribes to its TanStack Store, same pattern as .