tanstack-hotkeys

IndexedCommit: af1d4ec0 pullsUpdated Feb 16, 2026

Type-safe keyboard shortcuts for the web. TanStack Hotkeys provides template-string bindings with full TypeScript autocomplete, a cross-platform `Mod` key that maps to Command on macOS and Control on

Install this reference
Reference

TanStack Hotkeys

Type-safe keyboard shortcuts for the web. TanStack Hotkeys provides template-string bindings with full TypeScript autocomplete, a cross-platform Mod key that maps to Command on macOS and Control on Windows/Linux, a singleton Hotkey Manager, multi-key sequences (Vim-style), hotkey recording for settings UIs, key state tracking, and platform-aware display formatting — all SSR-safe.

Note: TanStack Hotkeys is currently in pre-alpha (v0.1.0). The API may change.

Quick References

FilePurpose
packages/hotkeys/src/index.tsCore package entry point (all framework-agnostic exports)
packages/react-hotkeys/src/index.tsReact adapter entry point (re-exports core + React hooks)
packages/hotkeys/src/hotkey.tsAll TypeScript types (Hotkey, ParsedHotkey, RawHotkey, etc.)
docs/framework/react/quick-start.mdReact quick start guide
docs/framework/react/guides/hotkeys.mdIn-depth useHotkey guide with all options

Packages

Packagenpm nameDescription
packages/hotkeys@tanstack/hotkeysCore framework-agnostic library (HotkeyManager, parsing, matching, formatting, sequences, recorder, key state tracker)
packages/react-hotkeys@tanstack/react-hotkeysReact adapter (hooks: useHotkey, useHotkeySequence, useHotkeyRecorder, useHeldKeys, useKeyHold, useHeldKeyCodes)
packages/hotkeys-devtools@tanstack/hotkeys-devtoolsFramework-agnostic devtools panel
packages/react-hotkeys-devtools@tanstack/react-hotkeys-devtoolsReact devtools plugin for TanStack DevTools

Each framework adapter package re-exports everything from @tanstack/hotkeys, so you only need to install the adapter.

When to Use

  • You need type-safe keyboard shortcuts with full TypeScript autocomplete for key combinations
  • You want cross-platform shortcuts (Mod+S → Command+S on Mac, Ctrl+S on Windows/Linux) without writing platform detection code
  • You need a centralized hotkey manager with conflict detection, scoped targets, and automatic input-element filtering
  • You're building settings UIs that let users record/customize their own keyboard shortcuts
  • You need Vim-style multi-key sequences (e.g., g g, d i w)
  • You want to display hotkeys in a platform-aware format (⌘⇧S on Mac, Ctrl+Shift+S on Windows)
  • You need real-time key state tracking for power-user UIs (e.g., showing held modifier indicators)

Installation

# React (includes core)
npm install @tanstack/react-hotkeys

# Core only (vanilla JS, no framework)
npm install @tanstack/hotkeys

# Devtools (optional, dev dependency)
npm install -D @tanstack/react-hotkeys-devtools

Best Practices

  1. Use Mod instead of Control or Meta for cross-platform shortcuts. Mod+S automatically resolves to Command+S on macOS and Ctrl+S on Windows/Linux.
  2. Don't wrap callbacks in useCallback — the useHotkey hook syncs callbacks on every render to prevent stale closures. Your callback always has access to the latest React state.
  3. Use enabled option for conditional hotkeys instead of conditionally calling the hook. This keeps hook call order stable (Rules of Hooks).
  4. Set tabIndex on elements used as target refs so they can receive keyboard events.
  5. Avoid Shift+punctuation combinations (e.g., Shift+,) as they produce different characters on different keyboard layouts. The type system prevents this.
  6. Use conflictBehavior: 'replace' when you intentionally want to override an existing hotkey from another component, or 'allow' to suppress warnings when multiple handlers are expected.
  7. Use formatForDisplay for UI labels rather than hardcoding key names — it renders platform-native symbols (⌘⇧S on Mac vs Ctrl+Shift+S on Windows).

Common Patterns

Basic hotkey registration (React):

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

function App() {
  useHotkey('Mod+S', (event, { hotkey, parsedHotkey }) => {
    saveDocument()
  })

  useHotkey('Escape', () => {
    closeDialog()
  })

  return <div>Press Cmd+S (Mac) or Ctrl+S (Windows) to save</div>
}

Using RawHotkey object instead of string:

useHotkey({ key: 'S', mod: true }, () => save())
useHotkey({ key: 'S', mod: true, shift: true }, () => saveAs())
useHotkey({ key: 'Escape' }, () => closeModal())

Conditional enable/disable:

function Modal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
  useHotkey('Escape', () => onClose(), { enabled: isOpen })

  if (!isOpen) return null
  return <div className="modal">Press Escape to close</div>
}

Scoped to a specific element:

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

function Panel() {
  const panelRef = useRef<HTMLDivElement>(null)

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

  return (
    <div ref={panelRef} tabIndex={0}>
      <p>Press Escape while focused here</p>
    </div>
  )
}

Vim-style multi-key sequences:

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

function VimEditor() {
  useHotkeySequence(['G', 'G'], () => scrollToTop())
  useHotkeySequence(['D', 'D'], () => deleteLine())
  useHotkeySequence(['D', 'I', 'W'], () => deleteInnerWord(), { timeout: 500 })

  return <div>Editor with Vim shortcuts</div>
}

Recording hotkeys for settings UIs:

import { useState } from 'react'
import { useHotkeyRecorder } from '@tanstack/react-hotkeys'
import type { Hotkey } from '@tanstack/react-hotkeys'

function ShortcutSettings() {
  const [shortcut, setShortcut] = useState<Hotkey>('Mod+S')

  const recorder = useHotkeyRecorder({
    onRecord: (hotkey) => setShortcut(hotkey),
    onCancel: () => console.log('Cancelled'),
    onClear: () => console.log('Cleared'),
  })

  return (
    <div>
      <span>Current: {shortcut}</span>
      <button onClick={recorder.startRecording}>
        {recorder.isRecording ? 'Press keys...' : 'Edit Shortcut'}
      </button>
    </div>
  )
}

Tracking held keys:

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

function StatusBar() {
  const isShiftHeld = useKeyHold('Shift')
  const heldKeys = useHeldKeys()

  return (
    <div>
      {isShiftHeld && <span>Shift mode active</span>}
      {heldKeys.length > 0 && <span>Keys: {heldKeys.join('+')}</span>}
    </div>
  )
}

Platform-aware display formatting:

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

function SaveButton() {
  useHotkey('Mod+S', () => save())

  return (
    <button>
      Save <kbd>{formatForDisplay('Mod+S')}</kbd>
      {/* Mac: "⌘S"  |  Windows: "Ctrl+S" */}
    </button>
  )
}

Global default options with HotkeysProvider:

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

function Root() {
  return (
    <HotkeysProvider
      defaultOptions={{
        hotkey: { preventDefault: true },
        hotkeySequence: { timeout: 1500 },
      }}
    >
      <App />
    </HotkeysProvider>
  )
}

Vanilla JS (without React):

import { getHotkeyManager, formatForDisplay } from '@tanstack/hotkeys'

const manager = getHotkeyManager()

const handle = manager.register('Mod+S', (event, context) => {
  console.log('Save triggered!')
})

// Update callback without re-registering
handle.callback = newCallback

// Update options
handle.setOptions({ enabled: false })

// Check state
console.log(handle.isActive) // true
console.log(manager.isRegistered('Mod+S')) // true

// Unregister
handle.unregister()

Devtools setup:

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

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

API Quick Reference

Core (@tanstack/hotkeys)

ExportTypeDescription
HotkeyManagerClassSingleton manager for hotkey registrations with per-target listeners
getHotkeyManager()FunctionGets the singleton HotkeyManager instance
KeyStateTrackerClassSingleton tracker for currently held keyboard keys
getKeyStateTracker()FunctionGets the singleton KeyStateTracker instance
HotkeyRecorderClassFramework-agnostic class for recording keyboard shortcuts
SequenceManagerClassSingleton manager for Vim-style multi-key sequences
getSequenceManager()FunctionGets the singleton SequenceManager instance
parseHotkey(hotkey, platform?)FunctionParses a hotkey string into a ParsedHotkey object
rawHotkeyToParsedHotkey(raw, platform?)FunctionConverts a RawHotkey object to ParsedHotkey
normalizeHotkey(hotkey, platform?)FunctionNormalizes a hotkey string to canonical form
parseKeyboardEvent(event)FunctionParses a KeyboardEvent into a ParsedHotkey
keyboardEventToHotkey(event)FunctionConverts a KeyboardEvent to a hotkey string
isModifier(key)FunctionChecks if a string is a modifier key name
isModifierKey(event)FunctionChecks if a KeyboardEvent is a modifier-only key press
hasNonModifierKey(hotkey, platform?)FunctionChecks if a hotkey contains at least one non-modifier key
convertToModFormat(hotkey, platform?)FunctionConverts platform-specific modifiers to portable Mod format
matchesKeyboardEvent(event, hotkey, platform?)FunctionChecks if a KeyboardEvent matches a hotkey
createHotkeyHandler(hotkey, callback, options?)FunctionCreates a keyboard event handler for a single hotkey
createMultiHotkeyHandler(handlers, options?)FunctionCreates a handler that matches multiple hotkeys
createSequenceMatcher(sequence, options?)FunctionCreates a simple sequence matcher for one-off use
formatHotkey(parsed)FunctionConverts a ParsedHotkey to a canonical hotkey string
formatForDisplay(hotkey, options?)FunctionFormats a hotkey for platform-aware UI display (⌘S on Mac, Ctrl+S on Windows)
formatWithLabels(hotkey, platform?)FunctionFormats with human-readable labels (Cmd+S on Mac, Ctrl+S on Windows)
formatKeyForDebuggingDisplay(key, options?)FunctionFormats a single key for devtools/debugging display
validateHotkey(hotkey)FunctionValidates a hotkey string, returns ValidationResult
assertValidHotkey(hotkey)FunctionValidates a hotkey and throws on error
checkHotkey(hotkey)FunctionValidates and logs warnings, returns boolean
detectPlatform()FunctionDetects current platform: 'mac', 'windows', or 'linux'
resolveModifier(modifier, platform?)FunctionResolves 'Mod' to 'Meta' or 'Control' based on platform
normalizeKeyName(key)FunctionNormalizes key aliases to canonical form (e.g., 'Esc''Escape')

React (@tanstack/react-hotkeys)

ExportTypeDescription
useHotkey(hotkey, callback, options?)HookRegisters a keyboard shortcut with automatic lifecycle management
useHotkeySequence(sequence, callback, options?)HookRegisters a multi-key Vim-style sequence
useHotkeyRecorder(options)HookReturns recording state and controls for capturing keyboard shortcuts
useHeldKeys()HookReturns array of currently held key names (reactive)
useHeldKeyCodes()HookReturns map of held key names to event.code values
useKeyHold(key)HookReturns boolean for whether a specific key is held
HotkeysProviderComponentContext provider for setting global default options
useHotkeysContext()HookAccess the HotkeysProvider context

Devtools (@tanstack/react-hotkeys-devtools)

ExportTypeDescription
hotkeysDevtoolsPlugin()FunctionCreates a plugin for @tanstack/react-devtools TanStackDevtools component
HotkeysDevtoolsPanelComponentStandalone devtools panel (auto-noop in production)

Key Types

TypeDescription
HotkeyType-safe union of all valid hotkey strings with autocomplete (e.g., 'Mod+S', 'Control+Shift+A', 'Escape')
RawHotkeyObject form for programmatic hotkey registration: { key, mod?, ctrl?, shift?, alt?, meta? }
RegisterableHotkeyHotkey | RawHotkey — accepted by useHotkey and HotkeyManager.register()
ParsedHotkeyParsed representation: { key, ctrl, shift, alt, meta, modifiers }
HotkeyCallback(event: KeyboardEvent, context: HotkeyCallbackContext) => void
HotkeyCallbackContext{ hotkey: Hotkey, parsedHotkey: ParsedHotkey }
HotkeyOptionsOptions for HotkeyManager.register(): enabled, preventDefault, stopPropagation, eventType, requireReset, ignoreInputs, conflictBehavior, target, platform
UseHotkeyOptionsReact version of HotkeyOptionstarget also accepts React.RefObject
HotkeyRegistrationHandleHandle returned by HotkeyManager.register() with unregister(), setOptions(), callback setter, isActive
HotkeySequenceArray<Hotkey> — sequence of hotkeys for Vim-style shortcuts
SequenceOptionsExtends HotkeyOptions with timeout (ms between keys, default 1000)
ConflictBehavior'warn' | 'error' | 'replace' | 'allow'
ValidationResult{ valid: boolean, warnings: string[], errors: string[] }
HeldKeyCanonicalModifier | Key — keys trackable as "held"
FormatDisplayOptions{ platform?: 'mac' | 'windows' | 'linux' }

Default Option Values

OptionDefaultNotes
enabledtrue
preventDefaulttruePrevents browser defaults like Ctrl+S save dialog
stopPropagationtrue
eventType'keydown'Can be 'keyup'
requireResetfalseWhen true, fires only once per press until key release
ignoreInputsSmart defaultfalse for Ctrl/Meta shortcuts and Escape; true for single keys and Shift/Alt combos
conflictBehavior'warn'
targetdocument
platformAuto-detected'mac', 'windows', or 'linux'
Sequence timeout1000 (ms)Time between keys in a sequence

“Explore distant worlds.”

© 2026 Oscar Gabriel