

# CommandPalette

⌘K-style searchable command overlay. You own `isOpen` / `onOpenChange` and typically close inside `onAction`. Items match on `title`, `meta`, and optional `keywords`. The panel uses overlay presence attributes (`data-open`, `data-starting-style`, `data-ending-style`); see [Overlay lifecycle](/docs/overlay-lifecycle). `onOpenChange` is still `(open: boolean)`.

## Examples

### Default

Open from a button (demos disable the global hotkey so pages stay calm).

```tsx
import { CommandPalette } from '@var-ui/react';

<CommandPalette
  isOpen={open}
  onOpenChange={setOpen}
  items={[{ id: 'docs', title: 'Docs', meta: 'Guides' }]}
  onAction={() => setOpen(false)}
/>
```

```astro
---
import { CommandPalette } from '@var-ui/astro';
---
<CommandPalette
  id="docs-command-palette"
  items={[{ id: 'docs', title: 'Docs', meta: 'Guides' }]}
/>
```

```html
<div data-var-ui-command-palette-root id="docs-command-palette">…</div>
```

### Grouped results via meta

Use `meta` (and `keywords`) so related commands are scannable and searchable even when titles are short.

```tsx
import { Button, CommandPalette } from '@var-ui/react';
import { useState } from 'react';

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

<Button intent="secondary" onPress={() => setOpen(true)}>
  Open palette
</Button>
<CommandPalette
  isOpen={open}
  onOpenChange={setOpen}
  hotkey={false}
  items={[
    { id: 'new-file', title: 'New file', meta: 'File', keywords: ['create'] },
    { id: 'theme', title: 'Toggle theme', meta: 'Appearance', keywords: ['dark'] },
    { id: 'settings', title: 'Open settings', meta: 'Navigation' },
  ]}
  onAction={() => setOpen(false)}
/>
```

```astro
<!-- React-only secondary demo — see default for Astro -->
```

```html
<!-- React-only secondary demo — see default for HTML -->
```

## Props

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| items | CommandPaletteItem[] | yes |  | Commands shown in the results list. |
| onAction | (id: string) => void | yes |  | Called with the selected item's `id` on Enter/click; the palette does not close itself. |
| emptyLabel | string \| undefined | no | 'No results' | Message shown when no items match the query. |
| filter | ((item: CommandPaletteItem, query: string) => boolean) \| undefined | no |  | Overrides the default case-insensitive title/meta/keywords match. |
| hotkey | boolean \| undefined | no | true | Toggles open state on ⌘K / Ctrl+K. |
| isOpen | boolean \| undefined | no |  | Controls the open state. |
| onOpenChange | ((open: boolean) => void) \| undefined | no |  | Called when the open state changes (Escape, backdrop click, hotkey, or item selection). |
| placeholder | string \| undefined | no | 'Search…' | Input placeholder. |
| portalContainer | Element \| undefined | no |  | Element the dialog renders into instead of its React parent. Needed when a subtree renders under a different theme than the page ambient (the theme's CSS custom properties only cascade to descendants of the themed element). |

## Accessibility

When open, focus moves to the search field; arrow keys move through results, Enter activates, Escape closes. Provide clear `title` text — `meta` is secondary. The default hotkey is ⌘K / Ctrl+K (`hotkey`); disable it in embedded demos if it would fight the host page.
