Overview
Use Command to help people find and run actions from a compact, keyboard-oriented list. It works inline as a persistent command browser or inside CommandDialog as a temporary command palette summoned with a shortcut such as Ctrl/Cmd+K.
Command is built on Base UI Autocomplete, so the input and results share highlight state and keyboard navigation. Use it for actions — opening a document, navigating to a workspace area, changing a setting. Use Combobox instead when the person is selecting a value for a field rather than running an action; the interaction feels similar but the outcome is data entry, and Combobox integrates with Field and forms.
Anatomy
Command includes the root panel, search input with optional leading icon, scrollable content area, items with optional leading and trailing icons, an empty state, optional groups with labels, and separators. The dialog variant wraps the same pieces in a root, trigger, viewport, and popup with a visually hidden title.
All parts and Tailwind styles live in one command.tsx file and use named PascalCase exports. The content area caps at 320 px tall and scrolls internally, so a long command list never grows the page or the popup beyond its viewport.
Behavior
Typing filters items. When you render CommandItem elements directly (the JSX style), Honest UI filters them by their value prop or visible text using a case-insensitive substring match. When you supply the root items prop instead, Base UI owns filtering and supports object values through itemToStringValue.
The first result is highlighted by default (autoHighlight="always"), so Enter always has a target. Groups, labels, and separators disappear during JSX filtering so results form one continuous list rather than scattered empty sections. The empty state appears only when no option remains.
Keep item labels action-oriented verbs ("Open documents", "Invite member"). Use the trailing icon slot for concise keyboard hints, not secondary descriptions that truncate on narrow panels.
Keyboard
Highlighting uses the combobox pattern: focus stays in the input while the highlighted option is announced through aria-activedescendant, so typing never loses focus.
Accessibility
CommandInput renders type="search" with role combobox and the default accessible name "Search commands." Pass a more specific aria-label when the scope is narrower, such as "Search billing actions," so screen-reader users know what the results will do. Results live in a listbox; each item is an option with aria-selected state managed for you.
The dialog content includes a visually hidden title (default "Command palette"); change it through the title prop so screen-reader users hear the same scope sighted users see in the placeholder.
If you add a global shortcut to open the palette, follow the pattern in the command dialog example: support both Control and Command, call preventDefault only for that shortcut, remove the listener when the component unmounts, and always keep a visible trigger — people who have never seen the shortcut need a discoverable way in.
The empty state matters here too: a blank panel after a typo reads as breakage. See Don't do this.
Colors come from theme tokens, so the panel, highlight, and placeholder adapt to dark mode automatically. Item labels truncate rather than wrap; keep them short enough to survive narrow panels and localized text expansion.
Installation
Usage
import {
Command,
CommandContent,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandLabel,
} from "@/components/ui/command";<Command>
<CommandInput placeholder="Search commands..." />
<CommandContent>
<CommandEmpty>No matching commands.</CommandEmpty>
<CommandGroup>
<CommandLabel>Workspace</CommandLabel>
<CommandItem value="new document">New document</CommandItem>
<CommandItem value="workspace settings">Workspace settings</CommandItem>
</CommandGroup>
</CommandContent>
</Command>Always include CommandEmpty. It renders only when filtering leaves no results, so there is no cost to having it and no excuse to omit it.
Don't do this
No guidance when nothing matches
// Bad
<CommandContent>
{items.map((item) => (
<CommandItem key={item.id}>{item.label}</CommandItem>
))}
</CommandContent>// Good
<CommandContent>
<CommandEmpty>
No matches for “{query}”. Try a different term or browse all commands.
</CommandEmpty>
{items.map((item) => (
<CommandItem key={item.id}>{item.label}</CommandItem>
))}
</CommandContent>A blank panel after a typo looks like the app froze, and people cannot tell whether their query matched nothing or the list failed to load. State what happened and what to try next — the empty string is part of the interface, not an edge case.
Destructive actions with no guardrail
// Bad
<CommandItem onClick={deleteWorkspace}>
Delete workspace
</CommandItem>// Good
<CommandItem onClick={() => setConfirmOpen(true)}>
Delete workspace…
</CommandItem>A palette makes destructive actions dangerously easy: one typed prefix, one press of Enter. Route irreversible commands through a confirmation step instead of executing immediately. The ellipsis in the label is a small cue that more will be asked before anything happens.
A palette nobody can find
// Bad
useEffect(() => {
// ⌘K opens the palette — nothing rendered anywhere
}, [])// Good
<CommandDialogTrigger render={<Button variant="secondary" />}>
Search actions <span className="text-xs opacity-72">⌘K</span>
</CommandDialogTrigger>Shortcuts are invisible until discovered. If the trigger exists nowhere on screen, people who never read your docs — which is most people — never learn the feature exists. Render a visible trigger with the shortcut hint next to it and teach the shortcut by exposure.
Examples
Inline command list
Grouping, filtering, keyboard hints in the trailing slot, icons, and visible action feedback announced through a polite live region.
Command dialog
A controlled palette bound to Ctrl/Cmd+K, with a visible trigger, a scoped hidden title, and closing after each command runs.
API reference
Command
Forwards Base UI Autocomplete root props plus:
Passing items switches filtering from Honest UI's JSX fallback to Base UI's own item filtering.
CommandInput
Accepts Base UI Autocomplete input props plus:
CommandItem
Accepts Base UI item props plus leadingIcon and trailingIcon. Without an explicit value, a plain-string child becomes the filterable value. onClick runs on Enter and on click.
Other parts
CommandContent, CommandEmpty, CommandGroup, CommandLabel, and CommandSeparator accept their matching Base UI element props. During JSX filtering, groups, labels, and separators hide themselves once a query is active.
Dialog parts
CommandDialog and CommandDialogTrigger forward Base UI Dialog root and trigger props. CommandDialogContent forwards popup props plus:
See the Base UI Autocomplete API and Base UI Dialog API for controlled state, item collections, filtering, and focus behavior.