Studio Keyboard Shortcuts
When Studio UI changes introduce or materially alter repeated user actions, consider whether keyboard shortcut coverage should be added or updated. Shortcuts use the shared Studio shortcut system and must be discoverable from the visible UI.
Rules
- Never add a one-off
keydownlistener for a normal Studio action — register it through the shortcut registry anduseShortcut. - Every registered shortcut is exposed where the action is visible, via
ShortcutTooltip,ShortcutBadge, or a command-menu badge. G then …chords are reserved for navigation.- Avoid broad
Mod+lettershortcuts that overlap common browser, editor, system, copy/save/search, or devtools behavior. - Before adding a shortcut, check the registry and any remaining non-registry listeners for collisions.
- Every search/filter
<Input>getsonKeyDown={onSearchInputEscape(...)}— see Search inputs.
Preferred pattern
- Add definitions in
apps/studio/state/shortcuts/registry.tsorapps/studio/state/shortcuts/registry/*. - Register with
useShortcut. - Gate availability with
enabled. - Surface visible actions with
ShortcutTooltiporShortcutBadge. - Prefer scoped, mnemonic sequential chords over global modifier chords.
- Set
showInSettings: falseon contextual shortcuts (scoped to a specific page state, sheet, or panel). - When a shortcut group should appear in the reference sheet (
Shift+?), add the group key toSHORTCUT_REFERENCE_GROUP_ORDERinapps/studio/state/shortcuts/referenceGroups.tsand a human label toGROUP_LABELSinShortcutsReferenceSheet.tsx. - For sheet-scoped shortcuts (active only while a
<Sheet>is open), mountuseShortcutinside the sheet component gated by theopenprop ({ enabled: open }) —apps/studio/components/interfaces/Platform/Webhooks/PlatformWebhooksDeliveryDetailsSheet.tsxis the canonical example. A shortcut that opens a sheet from anywhere is global instead, gated by whatever makes the action valid (e.g.useConnectSheetShortcutchecks project health).
Search inputs
Every <Input> used as a search or filter field must include the staged-Escape handler from apps/studio/lib/keyboard.ts:
import { onSearchInputEscape } from '@/lib/keyboard'
;<Input
value={query}
=> setQuery(e.target.value)}
setQuery)}
/>
Behavior:
- Escape while the input has a value → clears the value, keeps focus (so a second Escape then blurs)
- Escape while the input is empty → blurs the input
- Stops propagation on Escape so the keystroke does not accidentally close a parent dialog or sheet
When pairing with useShortcut(LIST_PAGE_FOCUS_SEARCH, ...) to focus a search input via keyboard, always also add onSearchInputEscape on the same input — focus and escape-to-blur are always a pair.
Key files
apps/studio/state/shortcuts/registry.ts, apps/studio/state/shortcuts/useShortcut.tsx, apps/studio/components/ui/Shortcut*.tsx, apps/studio/lib/keyboard.ts.
Tests
E2E tests for a feature with shortcuts cover both click interactions and the keyboard path — see studio-e2e-tests.