@tanstack/hotkeys
Version:
Type-safe, framework-agnostic keyboard hotkey management for the browser
63 lines (53 loc) • 3.97 kB
Markdown
---
name: app-hotkeys
description: >-
Use when an app built with TanStack Hotkeys needs a keyboard shortcut cheat
sheet or help dialog, a command palette whose commands may have shortcuts, a
settings screen where users rebind, unbind, or reset shortcuts, recording the
shortcut a user presses, conflict checks between shortcuts, or saving custom
shortcuts to storage.
metadata:
purpose: >-
Declare every shortcut once with defineAppHotkeys, then build cheat sheets,
rebinding, conflict checks, and persistence from those declarations instead
of a parallel shortcut registry.
type: core
library: '@tanstack/hotkeys'
library_version: '0.16.0'
sources:
- src/app-hotkeys.ts
- src/hotkey-recorder.ts
- src/conflicts.ts
- TanStack/hotkeys:docs/framework/react/guides/app-hotkeys.md
- TanStack/hotkeys:docs/framework/react/guides/cheat-sheet-ui-helpers.md
- TanStack/hotkeys:docs/framework/react/guides/custom-hotkey-recording.md
- 'TanStack/hotkeys:packages/react-hotkeys/tests/skills/shortcut-settings/**'
requires:
- hotkeys-core
---
# App hotkeys
This skill builds on `hotkeys-core`. The adapter skill's app hotkeys reference shows the wiring in your framework.
App hotkeys are the library's shortcut catalog. Use them instead of a separate shortcut config, context, or hand-written cheat sheet. One module declares every shortcut. The root provider holds the user's custom hotkeys. Components register the declared app hotkeys with their callbacks. Plain hotkeys with `meta` are enough when a list only needs what is mounted right now.
```ts
import { defineAppHotkeys } from '@tanstack/hotkeys'
export const appHotkeys = defineAppHotkeys({
'doc.save': { hotkey: 'Mod+S', meta: { name: 'Save' }, scope: 'document' },
'comment.post': {
hotkey: 'Mod+S',
meta: { name: 'Post comment' },
scope: 'comments',
},
'theme.toggle': { hotkey: null, meta: { name: 'Toggle theme' } },
})
```
Augment `Register` in the adapter module you import from, such as `declare module '@tanstack/react-hotkeys' { interface Register { appHotkeys: typeof appHotkeys } }`, to type ids. Lit, Alpine, and Ember have no provider: call `provideAppHotkeys(appHotkeys, options)` once at startup.
## Pitfalls
- **Register the app hotkey, not its value.** Pass `appHotkeys['doc.save']`. Passing `.hotkey` registers the declared default, so the user's rebinding never applies, and the list reports the shortcut as `'inactive'`.
- **The provider reads `customHotkeys` once.** When saved shortcuts load asynchronously, render the provider after they arrive, or key it by user. Change shortcuts afterwards through the adapter's custom hotkeys API, not by updating the prop.
- **Save what `onCustomHotkeysChange` receives.** It holds only the shortcuts the user changed, so users still get new defaults. Saving every effective hotkey freezes today's defaults into their data.
- **Ids are a storage contract.** Saved data is keyed by id, and unknown ids are dropped with a warning, so renaming an id discards users' customizations.
- **`setCustomHotkey` returns conflicts and changes nothing when there are any.** Check the result before reporting success, or reject the key press earlier by calling `findConflicts` in the recorder's `validate`.
- **Declare scopes on app hotkeys that share keys.** An app hotkey without a scope, or in `'global'`, conflicts with every other app hotkey on the same keys, so rebinding is rejected.
- **Conflict checks treat physical and logical bindings as different.** A recorded `Mod+[KeyP]` doesn't conflict with a declared `Mod+P`, although Command+P runs both. Declaring rebindable defaults in the recorder's form, such as `Mod+[KeyP]`, makes them comparable.
- **A cheat sheet dialog usually gets an exclusive scope.** Register the hotkey that opened it inside the dialog too, so the same keys close it.
When building the "press a new shortcut" control, read [recording](references/recording.md).