## Command Palette (S—004)

A ⌘K command menu with fuzzy matching, nested pages, grouped results and full keyboard control.

- URL: https://sundaeui.dev/components/command-palette
- Category: Navigation & menus · Art direction: Refined
- Access: Sundae Pro (authenticated install)
- Stack: React 18+, TypeScript, plain CSS; dependencies: none
- Install: `npx shadcn@latest add https://sundaeui.dev/r/command-palette.json`
- Tags: keyboard, search, command-palette, react, menu, dialog

A quiet, precise command menu in the tradition of good desktop software. Results are matched with a subsequence fuzzy scorer that rewards word starts and consecutive letters, and the matched characters are underlined so people can see why something matched.
Items can open nested pages, such as Theme → Dark. A breadcrumb shows where you are, and Backspace on an empty query takes you back. The list height animates as results change, so the panel never jumps.
It is built on a native <dialog> with a proper ARIA combobox and listbox. Focus is trapped and restored by the browser, Escape steps back a page before closing, and ⌘K or Ctrl+K toggles it from anywhere.

### Usage

```tsx
import { CommandPalette } from '@/components/sundae/command-palette'

export function AppCommands() {
  const router = useRouter()
  return (
    <CommandPalette
      items={[
        { id: 'new', group: 'Create', label: 'New document', shortcut: ['⌘', 'N'], onSelect: createDoc },
        { id: 'billing', group: 'Navigate', label: 'Go to billing', keywords: ['invoice'], onSelect: () => router.push('/billing') },
        {
          id: 'theme', group: 'Preferences', label: 'Change theme',
          children: [
            { id: 'light', label: 'Light', onSelect: () => setTheme('light') },
            { id: 'dark', label: 'Dark', onSelect: () => setTheme('dark') },
          ],
        },
      ]}
    />
  )
}
```

### Props

- `items` (`CommandItem[]`, required): Commands: { id, label, group?, keywords?, icon?, shortcut?, children?, onSelect? }.
- `open` (`boolean`): Controlled open state.
- `onOpenChange` (`(open: boolean) => void`): Called when the palette opens or closes.
- `hotkey` (`boolean`, default `true`): Toggle with ⌘K or Ctrl+K.
- `placeholder` (`string`, default `'Type a command or search…'`): Input placeholder on the root page.
- `inline` (`boolean`): Render the panel inline instead of as a modal, for embedding or settings pages.
- `emptyMessage` (`string`, default `'No results.'`): Shown when nothing matches.

### Accessibility

- The input is a role="combobox" that controls a role="listbox". The active option is announced via aria-activedescendant while focus stays in the input.
- Uses a native <dialog> opened with showModal(), so focus trapping, the inert background and focus restoration come from the browser.
- Escape steps back one nested page before closing the dialog.
- Emacs-style Ctrl+N and Ctrl+P work alongside the arrow keys.
