Skip to documentation content

Command

Search and run actions from an inline list or command palette.

command-demo

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

KeyResult
ArrowDown / ArrowUpMove the highlight through results
EnterRuns the highlighted item
EscapeClears the query when the list is closed; closes the dialog variant while open
Home / EndMove the text cursor within the query

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

npx honestui@latest add command

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-demo

Command dialog

A controlled palette bound to Ctrl/Cmd+K, with a visible trigger, a scoped hidden title, and closing after each command runs.

command-dialog

API reference

Command

Forwards Base UI Autocomplete root props plus:

PropValuesDefault
itemsreadonly array—
value / defaultValue / onValueChangestringuncontrolled
openbooleantrue
inlinebooleantrue
autoHighlight"always", "never", …"always"
keepHighlightbooleantrue

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:

PropValuesDefault
leadingIconReactNode—
sizesm, default, lglg
placeholderstring"Search..."
autoFocusbooleantrue
aria-labelstring"Search commands"

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:

PropValuesDefault
titlestring"Command palette"
widthCSS width540px, max 90vw

See the Base UI Autocomplete API and Base UI Dialog API for controlled state, item collections, filtering, and focus behavior.