tanstack-hotkeys-guide
Purpose
TanStack Hotkeys is a type-safe, framework-agnostic library for handling keyboard shortcuts. It provides React hooks for registering hotkeys, multi-key sequences, recording custom shortcuts, tracking key state, and platform-aware display formatting. The library uses event.key as its primary API with event.code fallback for letter/digit keys. Currently in alpha -- API may change.
When NOT to use: For a single shortcut listener, a plain addEventListener('keydown', ...) may suffice. Reach for TanStack Hotkeys when you need multiple shortcuts, cross-platform Mod handling, sequences, recording, or key-state tracking.
Instructions
- Always use
Modinstead of platform-specificMetaorControlfor cross-platform shortcuts - Import from
@tanstack/react-hotkeysfor React projects (it re-exports everything from@tanstack/hotkeys) - Import from
@tanstack/hotkeysonly for vanilla JS without React - Prefer the string form (
'Mod+S') over RawHotkey objects unless the hotkey is dynamic or built programmatically - Always add
tabIndex={0}to elements used astargetrefs -- they must be focusable to receive keyboard events - When building shortcut customization UIs, combine
useHotkeyRecorderwithformatForDisplayfor recording and display - The recorder auto-converts platform keys to portable
Modformat -- do not manually convert recorded hotkeys - Do NOT combine
ModwithControlorModwithMeta-- these create duplicate modifiers on one platform - Avoid
Alt+lettershortcuts for cross-platform apps -- macOS produces special characters with Option+letter - Avoid
Shift+numberandShift+punctuationshortcuts -- results are keyboard-layout-dependent - Warn the user that TanStack Hotkeys is in alpha when recommending it for production use
- Default
preventDefault: trueandstopPropagation: trueare intentional -- override explicitly only when needed - Use
conflictBehavior: 'allow'for intentional duplicate hotkeys, not to silence bugs
Installation
npm install @tanstack/react-hotkeys
The React package re-exports everything from @tanstack/hotkeys. No separate core install needed. For vanilla JS only, install @tanstack/hotkeys directly.
Optional devtools:
npm install @tanstack/react-devtools @tanstack/react-hotkeys-devtools
Quick Start
import { useHotkey } from '@tanstack/react-hotkeys'
function App() {
useHotkey('Mod+S', () => saveDocument())
return <div>Press Cmd+S (Mac) or Ctrl+S (Windows) to save</div>
}
Mod resolves to Meta (Cmd) on macOS and Control on Windows/Linux.
React Hooks Reference
useHotkey(hotkey, callback, options?)
Register a keyboard shortcut. Auto-syncs callback every render (no stale closures). Auto-unregisters on unmount.
useHotkey('Mod+S', (event, { hotkey, parsedHotkey }) => {
save()
})
Accept a string ('Mod+S') or RawHotkey object ({ key: 'S', mod: true }).
useHotkeySequence(sequence, callback, options?)
Register Vim-style multi-key sequences. Each step can include modifiers.
useHotkeySequence(['G', 'G'], () => scrollToTop())
useHotkeySequence(['Mod+K', 'Mod+C'], () => commentSelection())
Options: { timeout: 1000, enabled: true }.
useHotkeyRecorder(options)
Record custom keyboard shortcuts for settings UIs. Auto-converts to portable Mod format.
const { isRecording, recordedHotkey, startRecording, stopRecording, cancelRecording } =
useHotkeyRecorder({ onRecord: (hotkey) => setShortcut(hotkey) })
Options: { onRecord, onCancel?, onClear? }. Escape cancels. Backspace/Delete clears.
useHeldKeys()
Return a reactive string[] of currently held key names.
useHeldKeyCodes()
Return a reactive Record<string, string> mapping key names to event.code values.
useKeyHold(key)
Return boolean for a specific key's hold state. Only re-renders when that key changes.
const isShiftHeld = useKeyHold('Shift')
useDefaultHotkeysOptions()
Return the current default options from HotkeysProvider context.
useHotkeysContext()
Return the full hotkeys context value, or null if outside a provider.
HotkeysProvider
Wrap the app to set global default options. Per-hook options override provider defaults.
import { HotkeysProvider } from '@tanstack/react-hotkeys'
<HotkeysProvider defaultOptions={{
hotkey: { preventDefault: true },
hotkeySequence: { timeout: 1500 },
hotkeyRecorder: { onCancel: () => console.log('cancelled') },
}}>
<App />
</HotkeysProvider>
Hotkey String Format
- Modifiers:
Control,Alt,Shift,Meta - Cross-platform:
Mod(Cmd on Mac, Ctrl on Windows/Linux) - Format:
Modifier+Modifier+Key-- e.g.,'Mod+Shift+S' - Single keys:
'Escape','Enter','F1','ArrowUp','A','1','/' - RawHotkey alternative:
{ key: 'S', mod: true, shift: true } Mod+ControlandMod+Metacombinations are NOT allowed (would duplicate modifiers)
useHotkey Options
| Option | Default | Description |
|---|---|---|
| enabled | true | Whether the hotkey is active |
| preventDefault | true | Call event.preventDefault() |
| stopPropagation | true | Call event.stopPropagation() |
| eventType | 'keydown' | 'keydown' or 'keyup' |
| requireReset | false | Fire only once per key press |
| ignoreInputs | smart | false for Mod+key and Escape; true for single keys and Shift/Alt combos |
| target | document | DOM element, document, window, or React ref |
| conflictBehavior | 'warn' | 'warn', 'error', 'replace', or 'allow' |
| platform | auto | Override: 'mac', 'windows', 'linux' |
Smart ignoreInputs default: Mod+key shortcuts and Escape fire in text inputs. Single keys and Shift/Alt combos are ignored. Button-type inputs (type="button/submit/reset") are NOT ignored -- shortcuts work on them.
Display Formatting
import { formatForDisplay, formatWithLabels, formatKeyForDebuggingDisplay } from '@tanstack/react-hotkeys'
formatForDisplay('Mod+S') // Mac: "⌘S" Windows: "Ctrl+S"
formatWithLabels('Mod+S') // Mac: "Cmd+S" Windows: "Ctrl+S"
formatKeyForDebuggingDisplay('Meta') // Mac: "⌘ Mod (Cmd)"
Options: { platform: 'mac' | 'windows' | 'linux' }.
Core Utilities (Vanilla JS)
Use these without React:
import {
parseHotkey, normalizeHotkey, validateHotkey,
createHotkeyHandler, createMultiHotkeyHandler, createSequenceMatcher,
getHotkeyManager, getKeyStateTracker, getSequenceManager,
} from '@tanstack/hotkeys'
parseHotkey('Mod+S')-- return ParsedHotkey objectnormalizeHotkey('cmd+s')-- return canonical form'Meta+S'validateHotkey('Alt+A')-- return{ valid, warnings, errors }createHotkeyHandler('Mod+S', callback)-- return event handler functioncreateMultiHotkeyHandler({ 'Mod+S': save, 'Mod+Z': undo })-- return single event handlercreateSequenceMatcher(['G', 'G'], { timeout: 500 })-- return{ match(), reset(), getProgress() }
Key Rules and Gotchas
- Library is in ALPHA -- API may change
Modis the recommended way to write cross-platform shortcuts- Default
preventDefault: trueandstopPropagation: true-- override explicitly if needed - When using
targetwith a ref, ensure the element hastabIndexfor focus - macOS may swallow keyup events for non-modifier keys when a modifier is held -- library handles this
- Window blur auto-clears all held keys to prevent "stuck" keys
conflictBehavior: 'warn'is default -- logs duplicates during developmentevent.keyis primary.event.codeis fallback for letter/digit keys whenevent.keyproduces special characters (macOS Option+letter, Shift+number)- Alt+letter on macOS may produce special characters.
validateHotkeywarns about this - Shift+number and Shift+punctuation produce layout-dependent characters (Shift+1 →
!) -- avoid these combos; use Mod+number instead - SSR:
detectPlatform()defaults to'linux'when navigator is undefined --Modresolves toControlduring server rendering - Canonical modifier order in normalized strings:
Control → Alt → Shift → Meta
Devtools Setup
import { TanStackDevtools } from '@tanstack/react-devtools'
import { hotkeysDevtoolsPlugin } from '@tanstack/react-hotkeys-devtools'
function App() {
return (
<div>
<TanStackDevtools plugins={[hotkeysDevtoolsPlugin()]} />
</div>
)
}
Devtools are no-op in production by default. Use /production import path for production debugging:
import { hotkeysDevtoolsPlugin } from '@tanstack/react-hotkeys-devtools/production'
Reference Files
references/api-reference.md-- Full API surface with types and interfaces. Read when you need exact type signatures, constructor parameters, or constant values.references/patterns.md-- 25 usage patterns with complete code examples. Read when generating component code, building UIs with hotkeys, or choosing between implementation approaches.references/troubleshooting.md-- Platform quirks, common issues, and solutions. Read when debugging hotkey issues, when the user reports unexpected behavior, or when working with macOS/SSR edge cases.
More from vcode-sh/vibe-tools
orpc-guide
>-
80tanstack-router-guide
>
10hono-guide
Guide for Hono, an ultrafast web framework built on Web Standards. Use when user asks to "create a Hono app", "build an API with Hono", "add Hono middleware", "deploy Hono to Cloudflare Workers", "use Hono RPC", "add auth to Hono", "validate requests in Hono", "use Hono JSX", or asks about Hono routing, context, streaming, WebSocket, CORS, testing, SSG, or multi-runtime deployment. Do NOT use for Express.js, Fastify, Koa, or Nest.js.
9tanstack-start-guide
>-
9shadcn-guide
Guide for shadcn/ui — the open-code component system for React. Use when user asks to install, configure, theme, or use shadcn/ui components, set up dark mode, create forms, build a registry, configure MCP, or set up RTL/monorepo/JavaScript mode. Covers 58+ components (Base UI and Radix variants), CLI commands, components.json, CSS variable theming, framework installation (Next.js, Vite, Astro, Remix, Laravel, Gatsby, TanStack), registry system, and form patterns. Do NOT use for Chakra UI, Material UI, Ant Design, or vanilla Tailwind without shadcn context.
8base-ui-guide
Guide for Base UI (@base-ui/react), an unstyled React component library for building accessible UIs. Use when user asks to "build a component with Base UI", "create a form with Base UI", "style Base UI components", "animate Base UI popover", "use Base UI dialog", "add Base UI select", "implement Base UI tabs", or asks about Base UI accessibility, composition, customization, styling, animation, TypeScript types, or any of its 35+ components. Covers all components (Dialog, Menu, Popover, Select, Combobox, Tabs, Accordion, Toast, Form, Field, Slider, etc.), styling patterns, animations, composition via render props, event customization, form integration, and utilities. Do NOT use for Radix UI (radix-ui), Material UI (MUI @mui/material), or Shadcn/ui - those are separate libraries with different APIs.
6