Skip to content

CommandPalette component

CommandPalette is the ⌘K overlay — a search box over a fuzzy-filtered, grouped list of actions and destinations, with full keyboard navigation. The useCommandPalette hook wires up the hotkey and open state.

Navigation19 propsAccessibility notes

Playground

Change a prop on the right and the preview updates. Switch to Code to copy what you see.

Recipes

Copy-paste patterns for the ways CommandPalette actually gets used — each one a real, self-contained example.

⌘K command menu

useCommandPalette wires up the ⌘/Ctrl+K hotkey and open state; spread getPaletteProps() onto CommandPalette. Items are grouped and fuzzy-filtered as you type.

const palette = useCommandPalette();

<Button variant="bordered" onPress={palette.open}>
  Search… (⌘K)
</Button>
<CommandPalette
  {...palette.getPaletteProps()}
  items={items}
  onSelect={(item) => run(item.id)}
/>

Grouped items with shortcuts

Each item carries a label, a group, optional icon, shortcut, description, and keywords for fuzzy matching beyond the visible label.

const items = [
  { id: "new", label: "Create invoice", group: "Actions", shortcut: ["⌘", "N"] },
  { id: "export", label: "Export CSV", group: "Actions", description: "Downloads the current view" },
  { id: "dashboard", label: "Go to dashboard", group: "Navigation", keywords: ["home"] },
  { id: "settings", label: "Open settings", group: "Navigation" },
];

Controlled open state

Skip the hook and drive it directly with isOpen/onClose when something other than the hotkey opens it — a toolbar button, a first-run tour.

const [open, setOpen] = useState(false);

<Button variant="bordered" onPress={() => setOpen(true)}>
  Open palette
</Button>
<CommandPalette
  isOpen={open}
  onClose={() => setOpen(false)}
  items={items}
  onSelect={(item) => run(item.id)}
/>

Example

command-palette.tsx
const { open, getPaletteProps } = useCommandPalette({ hotkey: "mod+k" });

<CommandPalette
  {...getPaletteProps()}
  items={commandItems}
  placeholder="Search…"
/>

Props

The full public API for CommandPalette. Every prop is stable and intentionally typed.

Behavior & states

isOpen

boolean

default: —

Whether the palette is shown.

onClose

() => void

default: —

Fired on Escape, outside click, and after a selection.

onSelect

(item) => void

default: —

Fired on activation, after the item's own onSelect.

filter

(items, query) => items

default: built-in fuzzy

Replace the matcher; return the items to show, already ordered.

isLoading

boolean

default: false

Spinner in the input; suppresses the empty message.

onQueryChange

(query) => void

default: —

Fired as the query changes.

closeOnEscape / closeOnOutsideClick / portal / container / zIndex / blur

—

default: —

Passed through to Backdrop.

hotkey

"mod+k" | string | false

default: "mod+k"

Global open/toggle shortcut. mod = ⌘ on Apple platforms, Ctrl elsewhere. false binds nothing.

defaultOpen

boolean

default: false

Initial open state.

onOpenChange

(open) => void

default: —

Fired when the open state changes.

Data

items

CommandPaletteItem[]

default: —

The commands and destinations.

query / defaultQuery

string

default: ""

Controlled / initial query.

emptyQueryItems

CommandPaletteItem[]

default: all items

List shown while the query is empty (recent / suggested).

Appearance

placeholder

string

default: "Type a command or search…"

Input placeholder.

emptyMessage

ReactNode

default: "No results"

Shown when nothing matches.

footer

ReactNode

default: —

Content below the list.

Layout & positioning

size

"sm" | "md" | "lg"

default: md

Palette width (480 / 600 / 720).

Accessibility

ariaLabel

string

default: "Command palette"

Accessible name for the dialog and listbox.

Styling

classNames

{ base?, header?, input?, list?, group?, item?, … }

default: —

Slot classes.

Accessibility

  • The dialog traps focus, restores it to the trigger on close, and closes on Escape.
  • The input owns a `role="listbox"` of results via `aria-controls`/`aria-activedescendant`; Up/Down move the active option, Enter runs it.
  • Result count changes are announced through a live region.