Inputs / SearchField & CommandPalette
SearchField & CommandPalette
A search box with a wobbly marker outline that boils while it has focus, grouped results on a taped sheet, highlighter matches and keycap shortcuts. The same field powers a ⌘K command palette.
Client componentWCAG AANew
import { CommandPalette, SearchField } from "@plop-ui/react";
<SearchField
label="Search the dispatch board"
shortcut="/"
groups={[incidents, units, people]} // { id, label, items: [{ id, title, sub, meta, lead }] }
recentKey="recent-searches"
onSelect={(item) => open(item)}
/>
<CommandPalette groups={[pages, people]} emptyGroups={[actions]} onSelect={run} /> // ⌘K / Ctrl KChange every prop live in the playground.
Props
SearchField
| Prop | Type | Default | What it does |
|---|---|---|---|
| label required | string | — | What you're searching, e.g. "Search components". Always read by screen readers. |
| className | string | none | — |
| defaultValue | string | none | — |
| emptyGroups | SearchGroup[] | none | Shown when the box is empty and focused, e.g. quick actions. Recent searches come first. |
| filter | boolean | false | — |
| focusOnMount | boolean | false | — |
| groups | SearchGroup[] | none | What can be found. Filtered as you type (title, sub, meta and keywords); pass `filter={false}` if you filter yourself, e.g. on a server. |
| hideLabel | boolean | false | Hide the label visually (it stays for screen readers). Use it in tight headers. |
| inline | boolean | false | — |
| limit | number | 4 | Max results per group. |
| loading | boolean | false | Hop dots instead of the eraser while results load. |
| onSelect | (item: SearchItem, group: SearchGroup) => void | none | Picked with Enter or a click. |
| onValueChange | (value: string) => void | none | — |
| placeholder | string | none | — |
| recentKey | string | none | Remember picks under this localStorage key and offer them as recent searches. |
| shortcut | string | none | A keycap hint, and the key that focuses the field from anywhere on the page. `"/"` is a good one. |
| size | "sm" | "md" | "lg" | none | `sm` for headers, `md` (default), `lg` for a page about finding things. |
| value | string | none | — |
CommandPalette
| Prop | Type | Default | What it does |
|---|---|---|---|
| groups required | SearchGroup[] | — | What can be found while typing. |
| onSelect required | (item: SearchItem, group: SearchGroup) => void | — | — |
| emptyGroups | SearchGroup[] | none | Shown before typing, e.g. quick actions. |
| label | string | none | — |
| onOpenChange | (open: boolean) => void | none | — |
| open | boolean | false | Controlled open state. Leave both out and the palette opens with the shortcut on its own. |
| placeholder | string | none | — |
| recentKey | string | none | — |
| shortcut | boolean | true | Toggle with Cmd+K on a Mac, Ctrl+K elsewhere. |
Use it well
DoGroup results by kind and give each a lead (a face, a status dot, an icon) so people can scan without reading.
Don'tHide the only way to reach a page inside the palette. It's a shortcut, not the map.
Accessibility
- The input is a combobox: aria-expanded, aria-controls and aria-activedescendant follow the arrow keys.
- Enter opens the active (or first) result; Escape clears the text, then closes.
- The result count is announced in a polite live region; group headings are never focusable.
- The palette is a native <dialog>, so focus stays inside and Escape closes it.