TanStack Hotkeys — Knowledge Reference

Status: Alpha (as of early 2026). Actively developed; API surface may evolve. Package: @tanstack/react-hotkeys (also available for Preact, Solid, Svelte, Angular, Vue, Lit, and vanilla JS) Docs: https://tanstack.com/hotkeys/latest/docs/overview

li

What This Library Is

TanStack Hotkeys is a type-safe keyboard shortcuts library for the web. It provides template-string bindings (e.g. 'Mod+Shift+S'), a cross-platform Mod key abstraction, a singleton HotkeyManager under the hood, and utilities for building cheatsheet UIs — all while staying SSR-friendly.

It is not a general-purpose key-event listener. It is purpose-built for registering, managing, displaying, and letting users customize keyboard shortcuts in applications.\

linke me


Core Concepts

The Mod Modifier

The Mod modifier is the library's main cross-platform abstraction. It maps to Command (⌘) on macOS and Control (Ctrl) on Windows/Linux. Using Mod instead of hard-coding Meta or Control means a single hotkey definition works everywhere.

useHotkey('Mod+S', () => save())
// Mac → ⌘S  |  Windows/Linux → Ctrl+S

Hotkey Definitions — Two Formats

Hotkeys can be defined as either a template string or a raw object:

// String format (most common)
useHotkey('Mod+Shift+S', handler)
useHotkey('Escape', handler)

// Object format (full control)
useHotkey({ key: 'S', mod: true, shift: true }, handler)

TypeScript provides autocomplete and validation for both formats — invalid key combinations produce type errors at compile time.

Singleton HotkeyManager

All registrations go through a single centralized HotkeyManager instance. This enables conflict detection, automatic input filtering, per-target listeners, and powers the devtools. You don't interact with it directly in React — the hooks abstract it away.


API Surface — React Hooks

1. useHotkey — The Primary Hook

Registers a single keyboard shortcut. Automatic lifecycle management (registers on mount, unregisters on unmount). Stale-closure prevention — callbacks always have access to latest React state.

import { useHotkey } from '@tanstack/react-hotkeys'

useHotkey('Mod+S', (event, context) => {
  console.log(context.hotkey)       // 'Mod+S'
  console.log(context.parsedHotkey) // { key: 'S', mod: true, ... }
  saveDocument()
})

Full options with defaults:

useHotkey('Mod+S', callback, {
  enabled: true,              // Toggle on/off without unregistering
  preventDefault: true,       // Calls event.preventDefault()
  stopPropagation: true,      // Calls event.stopPropagation()
  eventType: 'keydown',       // Or 'keyup' (e.g. for push-to-talk release)
  requireReset: false,        // If true, must release all keys before re-triggering
  ignoreInputs: undefined,    // Smart default (see below)
  target: document,           // DOM element or React ref to scope the hotkey
  platform: undefined,        // Auto-detected; override with 'mac' | 'windows' | 'linux'
  conflictBehavior: 'warn',   // What happens when two hotkeys collide
  meta: {},                   // Arbitrary metadata (visible in devtools/registrations)
})

2. useHotkeys (plural) — Batch Registration

Register multiple hotkeys at once, or a dynamic/variable-length list:

import { useHotkeys } from '@tanstack/react-hotkeys'

useHotkeys([
  { hotkey: 'Mod+S', callback: () => save() },
  { hotkey: 'Mod+Z', callback: () => undo() },
  { hotkey: 'Escape', callback: () => close() },
])

Useful when hotkey bindings are stored in state (e.g. user-customizable shortcuts).

3. useHotkeySequence — Multi-Step Shortcuts

Vim-style key sequences where keys are pressed one after another (not simultaneously):

import { useHotkeySequence } from '@tanstack/react-hotkeys'

useHotkeySequence(['G', 'G'], () => scrollToTop())          // press g then g
useHotkeySequence(['G', 'Shift+G'], () => scrollToBottom())  // press g then G

Sequences have a configurable timeout between steps (default can be set via HotkeysProvider).

4. useKeyHold — Is a Specific Key Held?

Returns a boolean indicating whether a specific key is currently pressed:

import { useKeyHold } from '@tanstack/react-hotkeys'

const isShiftHeld = useKeyHold('Shift')
// true while Shift is physically held down

Use case: Showing alternate tooltips, switching tool modes, enabling "power user" overlays while a modifier is held.

5. useHeldKeys / useHeldKeyCodes — All Currently Held Keys

Returns an array of all keys currently pressed:

import { useHeldKeys } from '@tanstack/react-hotkeys'

const heldKeys = useHeldKeys()
// e.g. ['Shift', 'A'] while both are held

6. useHotkeyRecorder — Let Users Customize Shortcuts

Provides all the logic for building a "press keys to record shortcut" UI:

import { useHotkeyRecorder, formatForDisplay } from '@tanstack/react-hotkeys'

const {
  isRecording,       // boolean — is the recorder listening?
  recordedHotkey,    // Hotkey | null — the captured combination
  startRecording,    // () => void
  stopRecording,     // () => void
  cancelRecording,   // () => void
} = useHotkeyRecorder({
  onRecord: (hotkey) => console.log('Captured:', hotkey),
  onCancel: () => console.log('Cancelled'),
  onClear: () => console.log('Cleared'),
})

Full pattern — user-customizable shortcut settings:

const [shortcuts, setShortcuts] = useState({ save: 'Mod+S', undo: 'Mod+Z' })
const [editingAction, setEditingAction] = useState(null)

const recorder = useHotkeyRecorder({
  onRecord: (hotkey) => {
    if (editingAction) {
      setShortcuts(prev => ({ ...prev, [editingAction]: hotkey }))
      setEditingAction(null)
    }
  },
  onCancel: () => setEditingAction(null),
})

// Register actual hotkeys with current (possibly user-changed) bindings
useHotkey(shortcuts.save, () => save())
useHotkey(shortcuts.undo, () => undo())

7. useHotkeySequenceRecorder — Record Multi-Step Sequences

Similar to useHotkeyRecorder but for recording key sequences (multiple steps).

8. useHotkeyRegistrations — Inspect All Active Shortcuts

Returns a live view of every registered hotkey and sequence. Useful for building shortcut palettes, help dialogs, or onboarding overlays:

import { useHotkeyRegistrations } from '@tanstack/react-hotkeys'

const { hotkeys } = useHotkeyRegistrations()
// hotkeys[0].options.meta?.name → 'Save'
// hotkeys[0].triggerCount → 3

Key Options Explained

ignoreInputs — Smart Input Handling

Controls whether hotkeys fire when focus is inside <input>, <textarea>, or contenteditable elements.

Smart default behavior (when ignoreInputs is undefined):

  • Hotkeys with Mod/Ctrl modifiers (like Mod+S) do fire inside inputs — these are commands, not text entry.

  • The Escape key does fire inside inputs.

  • Single character keys (like K, G) do not fire inside inputs — they'd conflict with typing.

  • Button-type inputs allow hotkeys to fire.

You can override explicitly: ignoreInputs: true (never fire in inputs) or ignoreInputs: false (always fire).

requireReset — Prevent Key-Repeat Spam

When true, the hotkey fires once and won't fire again until all keys in the combination are released. Essential for toggle actions or commands that shouldn't repeat on hold.

target — Element Scoping

Scope a hotkey to a specific DOM element (only fires when that element or its children have focus):

const panelRef = useRef(null)
useHotkey('Escape', () => closePanel(), { target: panelRef })

return <div ref={panelRef} tabIndex={0}>...</div>

enabled — Conditional Activation

Dynamically enable/disable hotkeys based on app state. Disabled hotkeys remain registered (visible in devtools) — only execution is suppressed:

useHotkey('Escape', () => closeModal(), { enabled: isModalOpen })

conflictBehavior

What happens when two registrations use the same key combination. Options include 'warn' (default, logs a warning) and 'replace'.


Display & Formatting Utilities

formatForDisplay — Platform-Aware Rendering

Converts a hotkey string into a human-readable, platform-appropriate label:

import { formatForDisplay } from '@tanstack/react-hotkeys'

// On macOS:
formatForDisplay('Mod+S')         // "⌘ S"
formatForDisplay('Mod+Shift+Z')   // "⌘ ⇧ Z"
formatForDisplay('Control+Alt+D') // "⌃ ⌥ D"

// On Windows/Linux:
formatForDisplay('Mod+S')         // "Ctrl+S"
formatForDisplay('Mod+Shift+Z')   // "Ctrl+Shift+Z"

Accepts an options object to override platform or switch between symbols and text labels.

Other Formatting Functions

  • formatHotkey — canonical string output

  • formatWithLabels — human-readable text labels (e.g. "Cmd" instead of ⌘)

  • normalizeHotkey / normalizeHotkeyFromParsed / normalizeHotkeyFromEvent — produce canonical hotkey strings for storage and comparison

  • parseHotkey — parse a string into a structured ParsedHotkey object


Provider & Global Defaults

Wrap your app with HotkeysProvider to set defaults for all hooks:

import { HotkeysProvider } from '@tanstack/react-hotkeys'

<HotkeysProvider
  defaultOptions={{
    hotkey: {
      preventDefault: true,
      ignoreInputs: false,
    },
    hotkeySequence: {
      timeout: 1500,
    },
    hotkeyRecorder: {
      onCancel: () => console.log('Recording cancelled'),
    },
  }}
>
  <App />
</HotkeysProvider>

Per-hook options always override provider defaults.


Devtools Integration

TanStack Hotkeys has dedicated devtools that let you inspect all registered hotkeys, see which keys are currently held, test shortcuts, and view trigger counts — all in real time:

import { TanStackDevtools } from '@tanstack/react-devtools'
import { hotkeysDevtoolsPlugin } from '@tanstack/react-hotkeys-devtools'

function App() {
  return (
    <div>
      {/* Your app */}
      <TanStackDevtools plugins={[hotkeysDevtoolsPlugin()]} />
    </div>
  )
}

Framework Support

The core library (@tanstack/hotkeys) is framework-agnostic. Official adapters exist for:

Framework

Package

React

@tanstack/react-hotkeys

Preact

@tanstack/preact-hotkeys

Solid

@tanstack/solid-hotkeys

Svelte

@tanstack/svelte-hotkeys

Angular

@tanstack/angular-hotkeys

Vue

@tanstack/vue-hotkeys

Lit

@tanstack/lit-hotkeys

Vanilla JS

@tanstack/hotkeys


What's Achievable — Feature Checklist

Use this when evaluating ideas against the library's capabilities:

Feature Idea

Supported?

How

Basic keyboard shortcuts (save, undo, etc.)

Yes

useHotkey('Mod+S', handler)

Cross-platform modifier keys

Yes

Mod maps to Cmd/Ctrl automatically

Shortcuts scoped to specific UI regions

Yes

target option with a ref

Conditional enable/disable

Yes

enabled option

Preventing conflicts with text input fields

Yes

ignoreInputs with smart defaults

Single-fire (no key-repeat)

Yes

requireReset: true

Fire on key release instead of press

Yes

eventType: 'keyup'

Vim-style multi-step sequences (g → g)

Yes

useHotkeySequence

Detecting which keys are held down

Yes

useKeyHold / useHeldKeys

User-customizable shortcut recording

Yes

useHotkeyRecorder

User-customizable sequence recording

Yes

useHotkeySequenceRecorder

Shortcut palette / help dialog data

Yes

useHotkeyRegistrations + meta

Platform-aware display labels (⌘ vs Ctrl)

Yes

formatForDisplay

Global default configuration

Yes

HotkeysProvider

Conflict detection

Yes

conflictBehavior option

Devtools for debugging

Yes

hotkeysDevtoolsPlugin

SSR compatibility

Yes

Built SSR-friendly by design

Chords (two keys pressed simultaneously)

Yes

'Mod+Shift+S' etc.

Arbitrary key combos without modifiers

Yes

Single keys like 'K', 'Escape', 'F12'

Mouse-click shortcuts

No

Keyboard only

Gamepad / controller input

No

Keyboard only

Gesture recognition

No

Keyboard only

Global OS-level hotkeys (outside browser)

No

Browser keyboard events only


Patterns Worth Knowing

Command Palette Data Source

Combine meta on registrations with useHotkeyRegistrations to power a command palette:

useHotkey('Mod+S', () => save(), {
  meta: { name: 'Save', description: 'Save the current document' },
})

// In your palette component:
const { hotkeys } = useHotkeyRegistrations()
// Each entry has .options.meta, .triggerCount, etc.

Dynamic Shortcut Binding from User Preferences

const [userShortcuts, setUserShortcuts] = useState(loadFromStorage())

useHotkeys(
  Object.entries(userShortcuts).map(([action, hotkey]) => ({
    hotkey,
    callback: () => executeAction(action),
    options: {
      meta: { name: action },
      enabled: !isRecording,
    },
  }))
)

Push-to-Talk Pattern

useHotkey('Space', () => startMic(), { eventType: 'keydown', requireReset: true })
useHotkey('Space', () => stopMic(),  { eventType: 'keyup' })

Modal Escape with Conditional Enable

useHotkey('Escape', () => closeModal(), { enabled: isModalOpen })

Important Caveats

  1. Alpha status — The library is under active development. APIs may change between releases. Pin your version in production.

  2. preventDefault is true by default — This is intentional (most hotkeys override browser behavior like Ctrl+S). If you want the browser's default action to happen alongside your handler, explicitly set preventDefault: false.

  3. stopPropagation is true by default — Same reasoning. Override if you need the event to continue bubbling.

  4. Key detection uses event.key — With event.code as a fallback for letter and digit keys when event.key produces special characters (e.g. macOS Option+letter combos).

  5. Hotkeys are input-filtered by default — Single-key shortcuts won't fire inside text fields unless you opt out. Modifier combos and Escape do fire in inputs by default.


Quick Reference Links

  • Overview: https://tanstack.com/hotkeys/latest/docs/overview

  • React Quick Start: https://tanstack.com/hotkeys/latest/docs/framework/react/quick-start

  • Hotkeys Guide: https://tanstack.com/hotkeys/latest/docs/framework/react/guides/hotkeys

  • Sequences Guide: https://tanstack.com/hotkeys/latest/docs/framework/react/guides/sequences

  • Hotkey Recording Guide: https://tanstack.com/hotkeys/latest/docs/framework/react/guides/hotkey-recording

  • Sequence Recording Guide: https://tanstack.com/hotkeys/latest/docs/framework/react/guides/sequence-recording

  • Formatting & Display: https://tanstack.com/hotkeys/latest/docs/framework/react/guides/formatting-display

  • Devtools: https://tanstack.com/hotkeys/latest/docs/devtools

  • GitHub: https://github.com/TanStack/hotkeys