Command Palette
Overview
Modal fuzzy command search with a configurable shortcut.
Pattern: Dialog plus Listbox. Status: stable.
When to use
- Global command search with a configurable shortcut, grouping, disabled commands, and fuzzy matching.
- Documentation or app launchers where Dialog focus behavior and collection navigation should be reused.
When not to use
- Plain site search that navigates to remote results without command execution.
- Combobox inputs where freeform typed value is the product data.
Import
ts
import { createCommandPalette } from 'ui-headless-runtime';Controller creation
Create Command Palette during component mount or setup, subscribe before rendering derived UI, and keep every cleanup returned by registrations or DOM binding.
Options
- Public options:
defaultQuery,dialog,getQuery,matcher,onQueryChange,shortcut,subscribeQuery. - Command grouping, disabled state, keywords, and actions belong to each registered
CommandItem. Controlled open state is configured through the nesteddialogoptions.
Snapshot
- Snapshot fields:
activeId,commands,empty,open,openControlled,query,queryControlled. - Dialog IDs and position are not part of
CommandPaletteSnapshot; consumers bind their own dialog elements.
Commands
- Component commands:
bind,bindShortcut,close,handleKeyDown,open,registerCommand,select,setQuery. - The controller has no public
togglecommand.
Events
- Events:
beforeClose,beforeOpen,close,open,afterClose,afterOpen,beforeSelect,queryChange,select,stateChange. - Selection payloads contain the registered command and typed details; query events contain the next query.
Change reasons
- Change reasons:
programmatic,input,keyboard,pointer,trigger,escape-key,outside-pointer,focus-out,selection,context-menu,hover,focus. - Palette query/selection commands use
CommandPaletteReason; inherited dialog lifecycle events useOpenChangeReason, with the configured shortcut reported askeyboard.
Controlled mode
Controlled palettes can keep query/open state in a router or app store while using runtime filtering.
Uncontrolled mode
Uncontrolled palettes own open state, query, active item, and filtered command list.
DOM binding
- Bind trigger, dialog content, input, and result options; register commands outside rendering loops when possible.
Required markup
- Render the palette as a labelled dialog with a listbox-like result area.
ARIA contract
- Use Dialog metadata plus active option state for results; disabled commands must remain announced correctly.
Keyboard interaction
- Control/Command+K: Toggle the palette.
- ArrowDown / ArrowUp: Move through filtered enabled commands.
- Home / End: Move to the first or last filtered enabled command.
- Enter: Run the active command.
- Escape: Close the palette.
- Type characters: Update the editable query and fuzzy-filter commands.
Focus behavior
- Opening moves focus to the search input; Arrow keys move active command; Enter selects.
Nested behavior
- Because it uses Dialog, nested overlays obey the same topmost Escape behavior.
Cleanup
- Release shortcut listeners and command registrations before destroy.
Minimal lifecycle example
ts
import { createCommandPalette } from 'ui-headless-runtime';
const controller = createCommandPalette();
const unsubscribe = controller.subscribe((snapshot) => {
console.log(snapshot);
});
console.log(controller.getSnapshot());
unsubscribe();
controller.destroy();The production demo loads the exact executable module from apps/demo/src/examples/command-palette.ts.
Edge cases
search: Fuzzy score filters commands.groups: Commands retain group metadata.empty: No-match state is explicit.
Limitations
- It does not index remote documents or perform async fetching; consumers provide commands.
Related links
API reference
See createCommandPalette.