Keybindings Storage
How keybindings persistence works with settings-backed storage.
What It Does
Keybindings are stored through the settings system (keybindings key in user settings) and synchronized via TRPC.
This page focuses on the UI/runtime components you should use with that storage layer.
When To Use
- You want shortcuts persisted across sessions/devices.
- You need a settings page where users can customize shortcuts.
- You want runtime handlers to always read the latest persisted shortcut values.
Prerequisites
- Keybindings server filter initialized.
- Settings schema filter includes keybindings schema (provided by keybindings package filters).
This page describes the standard kit integration path; adapt app-specific paths and config names when your project differs.
Components To Use
| Component / API | Purpose | Typical place |
|---|---|---|
KeybindingsProvider | Provides keybinding model + storage to all keybinding hooks/components. | Dashboard app root/provider tree |
KeybindingsTable | Full settings UI to view/edit/reset shortcuts. | Settings page (/settings/keybindings) |
useKeybinding | Binds shortcut events to runtime handlers. | Feature/client components handling actions |
useShortcut | Reads effective shortcut (user override or default). | Labels, tooltips, command menus |
KeybindingDisplay | Displays shortcut for a model action (actionSlug). | Buttons, menu items, help panels |
ShortcutDisplay | Displays an already-known shortcut string. | Tables/lists/custom rows |
DatabaseKeybindingsStorage | Persists keybindings in user settings through TRPC. | Provider storage when not injected by filters |
LocalStorageKeybindingsStorage | Local browser fallback storage. | Local-only apps or quick prototypes |
Storage Modes
Local Storage vs Database Storage
| Dimension | LocalStorageKeybindingsStorage | DatabaseKeybindingsStorage |
|---|---|---|
| Persistence location | Browser localStorage | User settings in database (keybindings setting) |
| Scope | Current browser only | User account (shared across sessions/devices) |
| Sync across devices | No | Yes |
| Requires authenticated backend | No | Yes (TRPC + settings API) |
| Offline behavior | Works while offline in same browser | Depends on backend availability for writes |
| Best fit | Demos, local tools, non-auth apps | Production dashboards and multi-device users |
Selection Rules
- Use
DatabaseKeybindingsStoragefor authenticated apps where shortcuts must follow the user account. - Use
LocalStorageKeybindingsStoragefor local-only usage, prototypes, or environments without backend persistence. - If
storageis not passed toKeybindingsProvider, it defaults toLocalStorageKeybindingsStorage.
With useKeybindingsFilters(), dashboard apps are wired to DatabaseKeybindingsStorage automatically.
You only need manual storage selection when composing your own provider tree.
How To Use
In app filters, call:
useKeybindingsFilters()on client.initKeybindingsServerFilters()on server.
These hooks/filters:
- inject keybindings schema into settings schema filters,
- add settings page UI for keybindings,
- wire database-backed
DatabaseKeybindingsStorageviaclientTrpc.
No separate custom keybindings route is required in the default architecture.
Integration Patterns
Use filter-based integration by default (recommended).
This is the standard dashboard setup:
useKeybindingsFilters()injectsKeybindingsProviderandDatabaseKeybindingsStorage.- It also injects a settings page using
KeybindingsTable.
You usually only need to define the model and register handlers with useKeybinding.
Register runtime behavior in client components.
'use client';
import { useKeybinding } from '@kit/keybindings/ui';
export function SearchShortcutHandler() {
useKeybinding('search.command', () => {
// open command menu
});
return null;
}Display shortcuts in your UI.
'use client';
import { KeybindingDisplay, useShortcut } from '@kit/keybindings/ui';
export function SearchButtonHelp() {
const { formattedShortcut } = useShortcut('search.command');
return (
<div className="flex items-center gap-2">
<span>Search</span>
<KeybindingDisplay actionSlug="search.command" />
<span className="text-muted-foreground text-xs">({formattedShortcut})</span>
</div>
);
}Use manual provider wiring only for custom app composition.
Use this when you do not rely on useKeybindingsFilters() injection:
'use client'; import { keybindingsRouter } from '@kit/keybindings/router/router'; import { DatabaseKeybindingsStorage } from '@kit/keybindings/storage/settings-storage/handler'; import { KeybindingsProvider } from '@kit/keybindings/ui'; import type { KeybindingsProviderProps } from '@kit/keybindings/ui'; import type { TrpcClientWithQuery } from '@creatorem/next-trpc/query-client'; export function AppKeybindingsProvider({ clientTrpc, keybindingsModel, children, }: { clientTrpc: TrpcClientWithQuery<typeof keybindingsRouter>; keybindingsModel: KeybindingsProviderProps<any>['model']; children: React.ReactNode; }) { const storage = new DatabaseKeybindingsStorage(clientTrpc); return ( <KeybindingsProvider model={keybindingsModel} storage={storage}> {children} </KeybindingsProvider> ); }
Use local-only storage explicitly:
'use client'; import { LocalStorageKeybindingsStorage } from '@kit/keybindings/storage/local-storage'; import { KeybindingsProvider } from '@kit/keybindings/ui'; import type { KeybindingsProviderProps } from '@kit/keybindings/ui'; export function LocalKeybindingsProvider({ keybindingsModel, children, }: { keybindingsModel: KeybindingsProviderProps<any>['model']; children: React.ReactNode; }) { return ( <KeybindingsProvider model={keybindingsModel} storage={new LocalStorageKeybindingsStorage()} > {children} </KeybindingsProvider> ); }
Filter API
Persistence works because keybindings filters inject schema/UI/provider wiring into settings and dashboard provider composition.
| Filter | Parameters | Return | Registered By (package file) | Initialized In (app entrypoint) | Environment |
|---|---|---|---|---|---|
display_trpc_provider_child_in_dashboard | { clientTrpc?: TrpcClientWithQuery<Router<typeof appRouter>>; url?: (s: string) => string; keybindingsModel?: ReturnType<typeof parseKeybindingsConfig> } | React.ReactNode | kit/keybindings/src/filters/use-filters.tsx | apps/dashboard/hooks/use-filters.ts (useKeybindingsFilters) | client |
get_settings_schema | {} | SettingsSchema | kit/keybindings/src/filters/use-filters.tsx | apps/dashboard/hooks/use-filters.ts (useKeybindingsFilters) | client |
get_settings_ui_config | { clientTrpc: TrpcClientWithQuery<Router<unknown>> } | ReturnType<typeof parseUISettingConfig> | kit/keybindings/src/filters/use-filters.tsx | apps/dashboard/hooks/use-filters.ts (useKeybindingsFilters) | client |
server_get_settings_schema | {} | SettingsSchema | kit/keybindings/src/filters/server-filters.ts | apps/dashboard/lib/init-server-filters.ts (initKeybindingsServerFilters) | server |
display_keybinding | { actionSlug: keyof KeybindingActions } | React.ReactNode | kit/keybindings/src/filters/use-filters-with-ctx.tsx | KeybindingsProvider context (injected by useKeybindingsFilters) | client |
get_shortcut | { actionSlug: keyof KeybindingActions } | { shortcut: string | null; formattedShortcut: string } | null | kit/keybindings/src/filters/use-filters-with-ctx.tsx | KeybindingsProvider context (injected by useKeybindingsFilters) | client |
apps/dashboard/hooks/use-filters.tsmust calluseKeybindingsFilters().apps/dashboard/lib/init-server-filters.tsmust callinitKeybindingsServerFilters().
MCP Context
capability: keybindings_persistence entrypoints: - kit/keybindings/src/filters/server-filters.ts - kit/keybindings/src/filters/use-filters.tsx - kit/keybindings/src/filters/use-filters-with-ctx.tsx - apps/dashboard/hooks/use-filters.ts - apps/dashboard/lib/init-server-filters.ts - kit/keybindings/src/router/* inputs: - action_id - shortcut outputs: - persisted_keybinding_map constraints: - persistence uses settings infrastructure side_effects: - updates user settings storage
Agent Recipe
- Ensure keybindings filters are initialized.
- Verify settings schema contains
keybindingskey at runtime. - Test update flow through keybindings settings UI.
Troubleshooting
- If values reset after reload, confirm TRPC and settings update route wiring.
- If keybindings page is missing, verify
useKeybindingsFilters()is called. - If
useKeybindingthrows provider errors, ensureKeybindingsProvideris mounted above the component tree.
Related
API Reference
KeybindingsStorage
| Prop | Type | Default |
|---|---|---|
getUserKeybindings* | function | |
setUserKeybinding* | function | |
resetKeybinding* | function | |
resetAllKeybindings* | function |
How is this guide?
Last updated on 3/27/2026