
Tanstack Hotkeys
- 130 installs
- 14 repo stars
- Updated March 2, 2026
- oakoss/agent-skills
Helps with ai & agent building tasks during AI-assisted development.
About
tanstack-hotkeys is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- tanstack-hotkeys
- AI & Agent Building
- AI-coding skill
Tanstack Hotkeys by the numbers
- 130 all-time installs (skills.sh)
- +3 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #3,633 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oakoss/agent-skills --skill tanstack-hotkeysAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 130 |
|---|---|
| repo stars | ★ 14 |
| Last updated | March 2, 2026 |
| Repository | oakoss/agent-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
TanStack Hotkeys
Overview
TanStack Hotkeys is a type-safe keyboard shortcuts library for React with template-string bindings, cross-platform Mod key abstraction (Cmd on macOS, Ctrl on Windows/Linux), and SSR-friendly utilities. It provides hooks for single hotkeys, multi-key sequences, shortcut recording, and real-time key state tracking, plus platform-aware display formatting.
When to use: Adding keyboard shortcuts to React apps, recording user-defined hotkeys, detecting held modifier keys, displaying platform-specific shortcut labels, implementing Vim-style key sequences.
When NOT to use: Non-React apps without a wrapper (core package exists but React is the primary target), complex input handling that needs full keymap management (consider a dedicated keymap library).
Quick Reference
| Pattern | API | Key Points |
|---|---|---|
| Single hotkey | useHotkey | Mod abstracts Cmd/Ctrl; auto-prevents default on combos |
| Multi-key sequence | useHotkeySequence | Vim-style sequences like g g; configurable timeout |
| Record shortcuts | useHotkeyRecorder | User-defined shortcuts; start/stop/cancel recording |
| Detect key held | useKeyHold | Boolean for specific key; optimized re-renders |
| Track all held keys | useHeldKeys | Array of currently pressed key names |
| Track held key codes | useHeldKeyCodes | Map of key names to physical event.code values |
| Global defaults | HotkeysProvider | Set default options for all hooks; per-hook overrides apply |
| Format for display | formatForDisplay | Platform-aware: macOS symbols vs Windows/Linux text |
| Scope to element | target option + ref | Requires tabIndex on target element for focus |
| Conditional hotkeys | enabled option | Dynamically enable/disable without unmounting |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
Missing tabIndex on scoped target element | Add tabIndex={-1} (or 0) to elements using target ref for focus |
Using Ctrl instead of Mod | Use Mod for cross-platform; it maps to Cmd (Mac) or Ctrl (Win/Linux) |
| Assuming hotkeys fire inside inputs | ignoreInputs defaults to smart detection; set false to force firing |
| Duplicate hotkey registrations without config | Set conflictBehavior: 'replace' to override, or 'error' to catch |
Forgetting preventDefault is auto-enabled | Modifier combos auto-prevent; set preventDefault: false to allow default |
Using keyup without requireReset | Combine eventType: 'keyup' with requireReset: true to fire once |
Delegation
If the tanstack-pacer skill is available, delegate throttling and debouncing tasks to it. TanStack Pacer is useful for rate-limiting hotkey callbacks.Otherwise, recommend: pnpm dlx skills add oakoss/agent-skills -s tanstack-pacer -a claude-code -y>
If the tanstack-devtools skill is available, delegate devtools setup to it.Otherwise, recommend: pnpm dlx skills add oakoss/agent-skills -s tanstack-devtools -a claude-code -y- Keyboard shortcuts and hotkeys: Use this skill
- Throttling/debouncing hotkey callbacks: Delegate to
tanstack-pacer - DevTools integration: Delegate to
tanstack-devtools
References
- React hooks and configuration
- Sequences and recording
- Formatting and display
Formatting and Display
formatForDisplay
Converts a hotkey string into a platform-appropriate display string. On macOS, modifiers render as symbols. On Windows/Linux, modifiers render as text.
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"
formatForDisplay('Control+Alt+D'); // "Ctrl+Alt+D"Platform Override
Force a specific platform rendering regardless of the user's OS:
import { formatForDisplay } from '@tanstack/react-hotkeys';
formatForDisplay('Mod+S', { platform: 'mac' }); // "⌘S"
formatForDisplay('Mod+S', { platform: 'windows' }); // "Ctrl+S"Display Components
Shortcut Badge
import { formatForDisplay } from '@tanstack/react-hotkeys';
function ShortcutBadge({ hotkey }: { hotkey: string }) {
return <kbd className="shortcut-badge">{formatForDisplay(hotkey)}</kbd>;
}
// <ShortcutBadge hotkey="Mod+S" /> → ⌘S (Mac) or Ctrl+S (Win)
// <ShortcutBadge hotkey="Mod+Shift+P" /> → ⇧⌘P (Mac) or Ctrl+Shift+P (Win)Menu Item with Hotkey
import { useHotkey, formatForDisplay } from '@tanstack/react-hotkeys';
function MenuItem({
label,
hotkey,
onAction,
}: {
label: string;
hotkey: string;
onAction: () => void;
}) {
useHotkey(hotkey, () => onAction());
return (
<div className="menu-item">
<span>{label}</span>
<span className="menu-shortcut">{formatForDisplay(hotkey)}</span>
</div>
);
}
// <MenuItem label="Save" hotkey="Mod+S" onAction={save} />
// <MenuItem label="Undo" hotkey="Mod+Z" onAction={undo} />Command Palette Item
import { formatForDisplay } from '@tanstack/react-hotkeys';
import type { Hotkey } from '@tanstack/react-hotkeys';
interface Command {
id: string;
label: string;
hotkey?: Hotkey;
action: () => void;
}
function CommandPaletteItem({ command }: { command: Command }) {
return (
<div className="command-item" onClick={command.action}>
<span>{command.label}</span>
{command.hotkey && <kbd>{formatForDisplay(command.hotkey)}</kbd>}
</div>
);
}macOS Modifier Symbols
On macOS, formatForDisplay converts modifiers to standard Apple symbols:
| Modifier | Symbol | Display |
|---|---|---|
| Command | ⌘ | Mod on Mac renders as ⌘ |
| Shift | ⇧ | Shift renders as ⇧ |
| Option | ⌥ | Alt renders as ⌥ |
| Control | ⌃ | Control renders as ⌃ |
On Windows/Linux, all modifiers display as text joined with +.
Packages
| Package | Description |
|---|---|
@tanstack/hotkeys-core | Framework-agnostic core utilities |
@tanstack/react-hotkeys | React hooks (re-exports core) |
@tanstack/hotkeys-devtools | DevTools for debugging hotkey state |
Install @tanstack/react-hotkeys for React projects — it includes the core package automatically.
React Hooks and Configuration
Installation
npm install @tanstack/react-hotkeysThe React package re-exports everything from @tanstack/hotkeys-core, so no separate core install is needed.
HotkeysProvider
Wraps your app to set default options for all hotkey hooks. Per-hook options override provider defaults.
import { HotkeysProvider } from '@tanstack/react-hotkeys';
function App() {
return (
<HotkeysProvider
defaultOptions={{
hotkey: {
preventDefault: true,
stopPropagation: true,
conflictBehavior: 'warn',
},
hotkeySequence: {
timeout: 1500,
},
hotkeyRecorder: {
onCancel: () => console.log('Recording cancelled'),
},
}}
>
<Editor />
</HotkeysProvider>
);
}HotkeysProvider is optional. Without it, hooks use built-in defaults.
useHotkey
Registers a single keyboard shortcut. Uses Mod to abstract Cmd (macOS) / Ctrl (Windows/Linux).
import { useHotkey } from '@tanstack/react-hotkeys';
function Editor() {
useHotkey('Mod+S', () => save());
useHotkey('Mod+Z', () => undo());
useHotkey('Mod+Shift+Z', () => redo());
useHotkey('Escape', () => closePanel());
return <div>Editor</div>;
}Options
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable/disable the hotkey dynamically |
preventDefault | boolean | smart | Auto-enabled for modifier combos; manual for single keys |
stopPropagation | boolean | false | Stop event from bubbling up the DOM |
eventType | `'keydown' \ | 'keyup'` | 'keydown' |
requireReset | boolean | false | Only fire once per key press (ignore key repeat) |
ignoreInputs | boolean | smart | Smart default ignores single keys in inputs, allows combos |
target | `RefObject<Element> \ | string` | document |
platform | `'mac' \ | 'windows' \ | 'linux'` |
conflictBehavior | `'warn' \ | 'replace' \ | 'error' \ |
Options Examples
import { useHotkey } from '@tanstack/react-hotkeys';
function AdvancedEditor() {
// Fire on keyup instead of keydown
useHotkey('Shift', () => deactivateShiftMode(), {
eventType: 'keyup',
});
// Only fire once per key press, not on key repeat
useHotkey('Escape', () => closePanel(), {
requireReset: true,
});
// Allow browser default behavior
useHotkey('Mod+P', () => customPrint(), {
preventDefault: false,
});
// Force single key to work inside text inputs
useHotkey('Enter', () => submitForm(), {
ignoreInputs: false,
});
// Replace existing registration instead of warning
useHotkey('Mod+S', () => customSave(), {
conflictBehavior: 'replace',
});
return <div>Advanced editor</div>;
}Element Scoping
Scope hotkeys to specific elements using the target option with a ref. The target element must have tabIndex to receive focus.
import { useRef } from 'react';
import { useHotkey } from '@tanstack/react-hotkeys';
function ScopedEditor() {
const editorRef = useRef<HTMLDivElement>(null);
useHotkey('Mod+S', () => saveEditor(), { target: editorRef });
useHotkey('Escape', () => exitEditor(), { target: editorRef });
return (
<div ref={editorRef} tabIndex={-1}>
<p>Hotkeys only work when this editor is focused</p>
</div>
);
}Use tabIndex={-1} for programmatic focus only (not in tab order), or tabIndex={0} to include in the natural tab order.
Conditional Hotkeys
Use the enabled option to dynamically enable/disable hotkeys without unmounting.
import { useState } from 'react';
import { useHotkey } from '@tanstack/react-hotkeys';
function ConditionalEditor() {
const [isEditing, setIsEditing] = useState(false);
useHotkey('Mod+S', () => save(), { enabled: isEditing });
useHotkey('Escape', () => setIsEditing(false), { enabled: isEditing });
useHotkey('E', () => setIsEditing(true), { enabled: !isEditing });
return <div>{isEditing ? <textarea /> : <p>Press E to edit</p>}</div>;
}Input Field Handling
By default, ignoreInputs uses smart detection:
- Single keys (e.g.,
Escape,Enter) are ignored inside<input>,<textarea>, and[contenteditable] - Modifier combos (e.g.,
Mod+S) fire even inside inputs
Override this behavior per-hook:
import { useHotkey } from '@tanstack/react-hotkeys';
function FormWithHotkeys() {
// Forces Enter to fire inside inputs
useHotkey('Enter', () => submitForm(), {
ignoreInputs: false,
});
// Mod+S works inside inputs by default (smart detection)
useHotkey('Mod+S', () => saveDraft());
return (
<form>
<input type="text" placeholder="Type here..." />
<button type="submit">Submit</button>
</form>
);
}Hotkey String Format
Hotkeys use + to join keys. Modifiers come first, then the key.
| Hotkey string | Description |
|---|---|
Mod+S | Cmd+S (Mac) or Ctrl+S (Win/Linux) |
Mod+Shift+Z | Redo shortcut |
Control+Alt+D | Explicit Ctrl (not Mod) |
Escape | Single key |
Shift | Modifier alone |
ArrowUp | Arrow keys |
F1 | Function keys |
Use Mod for cross-platform shortcuts. Use Control or Meta only when you need a specific physical key regardless of platform.
Sequences and Recording
useHotkeySequence
Registers a multi-key sequence (Vim-style) that triggers when all keys are pressed in order within a timeout window.
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>Vim-style editor</div>;
}Sequence Options
| Option | Type | Default | Description |
|---|---|---|---|
timeout | number | 1000 | Max milliseconds between keys before sequence resets |
enabled | boolean | true | Enable/disable the sequence dynamically |
Sequences with Modifiers
Each step in a sequence can include modifier keys:
import { useHotkeySequence } from '@tanstack/react-hotkeys';
function EditorWithChords() {
// Ctrl+K then Ctrl+C (VS Code-style comment toggle)
useHotkeySequence(['Mod+K', 'Mod+C'], () => toggleComment());
// Leader key pattern: Space then F to find
useHotkeySequence([' ', 'F'], () => openFindDialog());
return <div>Editor with chord sequences</div>;
}useHotkeyRecorder
Lets users record their own keyboard shortcuts. Captures the key combination pressed and returns it as a hotkey string.
import { useHotkeyRecorder, formatForDisplay } from '@tanstack/react-hotkeys';
function ShortcutRecorder() {
const {
isRecording,
recordedHotkey,
startRecording,
stopRecording,
cancelRecording,
} = useHotkeyRecorder({
onRecord: (hotkey) => {
console.log('Recorded:', hotkey);
},
});
return (
<div>
<button onClick={isRecording ? stopRecording : startRecording}>
{isRecording
? 'Press a key combination...'
: recordedHotkey
? formatForDisplay(recordedHotkey)
: 'Click to record'}
</button>
{isRecording && <button onClick={cancelRecording}>Cancel</button>}
</div>
);
}Recorder Return Value
| Property | Type | Description |
|---|---|---|
isRecording | boolean | Whether recording is active |
recordedHotkey | `string \ | null` |
startRecording | () => void | Begin capturing keystrokes |
stopRecording | () => void | Stop and finalize recording |
cancelRecording | () => void | Stop without saving |
Recorder Options
| Option | Type | Default | Description |
|---|---|---|---|
onRecord | (hotkey: string) => void | — | Called when recording completes |
onCancel | () => void | — | Called when recording is cancelled |
onClear | () => void | — | Called when recorded hotkey is cleared |
Settings Panel Example
import { useState } from 'react';
import {
useHotkey,
useHotkeyRecorder,
formatForDisplay,
} from '@tanstack/react-hotkeys';
function ShortcutSettings() {
const [shortcuts, setShortcuts] = useState<Record<string, string>>({
save: 'Mod+S',
find: 'Mod+F',
undo: 'Mod+Z',
});
const [editingAction, setEditingAction] = useState<string | null>(null);
const { isRecording, startRecording, cancelRecording } = useHotkeyRecorder({
onRecord: (hotkey) => {
if (editingAction) {
setShortcuts((prev) => ({ ...prev, [editingAction]: hotkey }));
setEditingAction(null);
}
},
onCancel: () => setEditingAction(null),
});
return (
<div>
<h3>Keyboard Shortcuts</h3>
{Object.entries(shortcuts).map(([action, hotkey]) => (
<div key={action}>
<span>{action}</span>
<button
onClick={() => {
setEditingAction(action);
startRecording();
}}
>
{editingAction === action && isRecording
? 'Press keys...'
: formatForDisplay(hotkey)}
</button>
</div>
))}
{isRecording && <button onClick={cancelRecording}>Cancel</button>}
</div>
);
}useKeyHold
Returns a boolean indicating whether a specific key is currently held down. Optimized to only re-render when the tracked key changes state.
import { useKeyHold } from '@tanstack/react-hotkeys';
function ShiftIndicator() {
const isShiftHeld = useKeyHold('Shift');
return (
<div style={{ opacity: isShiftHeld ? 1 : 0.5 }}>
{isShiftHeld ? 'Shift is pressed!' : 'Press Shift'}
</div>
);
}Hold-to-Reveal Pattern
import { useKeyHold } from '@tanstack/react-hotkeys';
function FileItem({ file }: { file: { name: string; id: string } }) {
const isShiftHeld = useKeyHold('Shift');
return (
<div className="file-item">
<span>{file.name}</span>
{isShiftHeld ? (
<button className="danger" onClick={() => permanentlyDelete(file.id)}>
Permanently Delete
</button>
) : (
<button onClick={() => moveToTrash(file.id)}>Move to Trash</button>
)}
</div>
);
}Shortcut Overlay Pattern
import { useKeyHold } from '@tanstack/react-hotkeys';
function ShortcutHints() {
const isModHeld = useKeyHold('Meta');
if (!isModHeld) return null;
return (
<div className="shortcut-overlay">
<div>S - Save</div>
<div>Z - Undo</div>
<div>Shift+Z - Redo</div>
<div>K - Command Palette</div>
</div>
);
}useHeldKeys
Returns an array of all currently pressed key names. Re-renders on every key change.
import { useHeldKeys } from '@tanstack/react-hotkeys';
function KeyDisplay() {
const heldKeys = useHeldKeys();
return (
<div>
{heldKeys.length > 0 ? `Held: ${heldKeys.join(' + ')}` : 'No keys held'}
</div>
);
}useHeldKeyCodes
Returns a record mapping key names to their physical event.code values. Useful for distinguishing left vs right modifier keys.
import { useHeldKeyCodes } from '@tanstack/react-hotkeys';
function KeyDebugDisplay() {
const heldCodes = useHeldKeyCodes();
return (
<ul>
{Object.entries(heldCodes).map(([keyName, keyCode]) => (
<li key={keyName}>
{keyName}: <small>{keyCode}</small>
</li>
))}
</ul>
);
}useHeldKeyCodes distinguishes physical keys like ShiftLeft vs ShiftRight, while useHeldKeys normalizes both to Shift.