Command Search
The search palette, results first — one-row scope chips with More, results to 520px, stale results dimmed while a search runs.
- Status
- Since
0.25.89- Accessibility pattern
- dialog, combobox, listbox; aria-busy while searching
Last updated
Search
Search or jump to…
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Command Search before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify command-searchInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/command-searchRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/command, @vegastack/dropdown-menu, @vegastack/notification-bell, @vegastack/toggle, @vegastack/toggle-group.
Usage
"use client";
import {
CommandSearch,
CommandSearchInput,
CommandSearchResults,
CommandSearchScopes,
CommandSearchSections,
} from "@/components/ui/command-search";
import {
CommandFooter,
CommandGroup,
CommandItem,
} from "@/components/ui/command";
import { useAsyncSearch } from "@/components/ui/use-async-search";
export function SearchPalette({ open, onOpenChange }: Props) {
const [scope, setScope] = React.useState("all");
const [archived, setArchived] = React.useState(false);
// Debounced and abortable; an answer that arrives after a newer request started is dropped.
const search = useAsyncSearch(
(q, { signal }) => api.search({ q, scope, archived }, { signal }),
{ enabled: open, deps: [scope, archived] },
);
return (
<CommandSearch
open={open}
onOpenChange={onOpenChange}
pending={search.loading}
>
<CommandSearchInput
value={search.query}
onValueChange={search.onSearchChange}
/>
<CommandSearchScopes
scopes={SCOPES}
value={scope}
onValueChange={setScope}
includeArchived={archived}
onIncludeArchivedChange={setArchived}
/>
<CommandSearchResults>
{search.query ? (
<Results items={search.items} />
) : (
<CommandSearchSections
recent={recent}
create={create}
places={places}
/>
)}
</CommandSearchResults>
<CommandFooter />
</CommandSearch>
);
}A search palette is for finding things, so the results get the room: the scope chips sit on one
line that never wraps — whatever does not fit goes into More, with "Include archived" — and the
results run to min(60dvh, 520px). While a new answer is on its way the old one stays, dimmed and
marked busy, with a thin bar running under the input, so stale results never read as the answer to
what was just typed. Pair it with useAsyncSearch, which drops out-of-order answers, and pass its
loading as pending.
Scope
- Owns: the palette frame (a
CommandDialogholding aCommandthat does not filter), the one-row scope chips and their More overflow, the archived toggle, the results' height cap, the in-flight dimming and progress bar, the start sections' order, and the preview pane. - Does not own: searching — the host's loader and
useAsyncSearch— the results' rows, opening a result, or recents. - Compose with:
CommandGroup,CommandItemandCommandEmptyfor the results, andCommandFooterfor key hints.
Anatomy
Examples
Results first
Open the palette: with nothing typed it lists Recent, Create and Places, in that order. Type to search — each answer takes 700ms here, so the bar under the input and the dimmed stale results are visible. Pick a scope from the chips or from More; a scope picked from More names its trigger.
Search
Search or jump to…
Preview pane
Pass preview with the highlighted result's summary and, from 1280px of viewport, the palette
widens and shows it beside the results. Below 1280px the pane is not shown.
Search
Search or jump to…
API Reference
CommandSearch
| Prop | Type | Default | Description |
|---|---|---|---|
description | string | "Search or jump to…" | The dialog's accessible description (visually hidden). |
dialogClassName | string | — | Classes for the dialog surface. |
onOpenChange | ((open: boolean) => void) | — | Fired when the palette asks to open or close. |
open | boolean | — | Controlled open state. |
pending | boolean | false | A search is in flight: the results still shown are stale, so they dim and are marked busy, and a thin bar runs under the input. |
preview | React.ReactNode | — | A preview of the highlighted result, in a pane beside the results from 1280px of viewport (the palette widens to hold it). Hidden below that. |
resultsLabel | ((count: number) => string) | — | |
shouldFilter | boolean | false | cmdk's own filtering. Off by default: a search palette shows the host's answer as given. |
title | string | "Search" | The dialog's accessible title (visually hidden). |
Data attributes and CSS variables on CommandSearch
| Attribute | Values |
|---|---|
data-pending | "" |
data-slot | "command-search" |
It also takes Command's props (value, onValueChange, onKeyDown, resultsLabel).
CommandSearchInput
CommandInput's props; the placeholder defaults to "Search or jump to…".
CommandSearchScopes
| Prop | Type | Default | Description |
|---|---|---|---|
onValueChange* | (value: string) => void | — | Fired with the scope picked from the chips or from More. |
scopes* | readonly CommandSearchScope[] | — | The scopes, in chip order; the first is usually "All". |
value* | string | — | The selected scope's value. |
aria-label | string | "Search in" | The chip group's accessible name. |
className | string | — | Classes for the row. |
includeArchived | boolean | false | Whether archived items are included. Shown in More only with onIncludeArchivedChange. |
includeArchivedLabel | string | "Include archived" | The archived item's text. |
maxVisible | number | — | At most this many chips before More, whatever the room. Omitted, as many as fit. |
moreLabel | string | "More" | The overflow menu's trigger text (it shows the picked scope's name instead while that scope sits in the menu). |
onIncludeArchivedChange | ((includeArchived: boolean) => void) | — | Fired by More's "Include archived" item; omit it and the item is not offered. |
Data attributes and CSS variables on CommandSearchScopes
| Attribute | Values |
|---|---|
data-icon | "inline-end" |
data-measure | "chip" | "more" |
data-slot | "command-search-archived-dot" | "command-search-more" | "command-search-more-content" | "command-search-scopes" | "command-search-scopes-ruler" |
data-state | "on" |
CommandSearchResults
CommandList's props.
CommandSearchSections
| Prop | Type | Default | Description |
|---|---|---|---|
create | React.ReactNode | — | Create actions — New page, New task (CommandItems). |
labels | { recent?: string; create?: string; places?: string; } | { recent: "Recent", create: "Create", places: "Places" } | Section headings. |
places | React.ReactNode | — | Places to jump to — Home, Tasks, Library (CommandItems). |
recent | React.ReactNode | — | Recently opened records and searches (CommandItems). Empty or omitted: no section. |
Data attributes and CSS variables on CommandSearchSections
| Attribute | Values |
|---|---|
data-section | mirrors a prop or state value |
data-slot | "command-search-section" |
Accessibility
- The palette is a labelled dialog around cmdk's combobox and listbox; the result count is
announced by
Command(resultsLabel). - While
pending, the results arearia-busy; the progress bar is decorative. - The visible chips are a toggle group named "Search in"; More is a menu button whose name is the scope it holds, and says "(Include archived)" while archived items are included.
- Enter, ↑/↓ and Home/End on a chip or in the More menu stay with that control instead of moving the results' selection.
| Key | Action |
|---|---|
| ↑ / ↓ | Moves through the results (from the input) |
| Enter | Opens the selected result |
| Tab | Moves to the scope chips and More |
| Escape | Closes the palette |
| Contract | States tested |
|---|---|
| Behaviour | default, scope-chip, scope-overflow, include-archived, pending, stale-results, sections, preview |
| Accessibility | dialog-semantics, listbox, busy, menu-semantics, browser-accessibility-test |
| Visual | default, pending, more-active, archived-on, with-preview |