Skip to content

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 nested dialog options.

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 toggle command.

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 use OpenChangeReason, with the configured shortcut reported as keyboard.

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.

API reference

See createCommandPalette.

Headless behavior; consumer-owned rendering and styling.