Skip to content

Command

Jumps to records, pages and commands from the keyboard, filtering as you type. For a form field, use Select or Combobox.

Library
What’s this?

Installation

pnpm dlx shadcn@latest add @wisty/command

The CLI installs the Radix or Base UI file to match your project, with what it builds on: dialog, input-group.

If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.

When to use

Use it

  • People search a mixed list of things to do or places to go, like recent records, actions and app pages, and pick one from the keyboard.
  • A command palette: a search box and its results in a dialog that opens from a button or a shortcut.
  • A choice made from a button that people search, like switching a list view from a popover. The choice acts at once and isn't kept as a form field's value.
  • A list that stays on the page with a search field above it, like a quick find in a side panel.

Don't use it

  • The value belongs to a form field, even one shown in a popover. Use Select for short lists or Combobox for long ones, which keeps the value in the field.
  • People pick Salesforce records with object icons and a meta line. Use Lookup.
  • The list holds a few actions on one thing, opened from a button. Use Dropdown menu.

Usage

Command
├── CommandInput
└── CommandList
    ├── CommandEmpty
    ├── CommandGroup
    │   └── CommandItem
    │       └── CommandShortcut
    └── CommandSeparator

Give Command a label, which names the search field.

import {
  Command,
  CommandEmpty,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command"
<Command label="Search Salesforce">
  <CommandInput placeholder="Search records..." />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandItem value="Rose Gonzalez" onSelect={openRecord}>
      Rose Gonzalez
    </CommandItem>
  </CommandList>
</Command>

onSelect gets the item's value, from Enter or a click. Items filter on their text, shortcut included, so pass value with the words people search for and keywords for other words that should match. For a palette, put a Command inside CommandDialog, as the In a dialog example shows; it has no close button unless you pass showCloseButton. For a choice from a button, put it inside PopoverContent with p-0, as the Selected and disabled example shows.

Examples

In a dialog

The palette in `CommandDialog`: a Search button opens it, and picking a record or app page closes it.

Search Salesforce

Selected and disabled

A list view switcher in `PopoverContent`, the current view checked with `aria-checked` and one view `disabled`.

No results

A search that matches nothing shows the `CommandEmpty` row.

Burlington Textiles Corp of America
Dickenson plc
Edge Communications

API reference

PartBuilt on
Commandcmdk Command (opens in a new tab)
CommandDialogDialog
CommandInputcmdk Command.Input (opens in a new tab)
CommandListcmdk Command.List (opens in a new tab)
CommandEmptycmdk Command.Empty (opens in a new tab)
CommandGroupcmdk Command.Group (opens in a new tab)
CommandSeparatorcmdk Command.Separator (opens in a new tab)
CommandItemcmdk Command.Item (opens in a new tab)
CommandShortcut<span> (opens in a new tab)

CommandDialog

PropTypeDefault
titlestring"Command Palette"
descriptionstring"Search for a command to run..."
showCloseButtonbooleanfalse

SLDS mapping

SLDS has no command palette, so Command is built from the parts it does have. It runs on cmdk in Radix and Base UI projects alike; cmdk depends on @radix-ui/react-dialog, so a Base UI project installs it and the Radix packages it needs too.

  • Command is slds-dropdown without its shadow. Inside CommandDialog it takes the modal's corners.
  • CommandInput is input group's search field, slds-input-has-icon_left.
  • CommandList is slds-listbox_vertical at slds-dropdown_length-7.
  • CommandItem is a plain listbox option, slds-listbox__option_plain, with SLDS's icon column. The check is SLDS's selected option check, slds-is-selected.
  • CommandGroup's heading is slds-listbox__option-header, and CommandSeparator is slds-has-divider_top-space. CommandEmpty is a plain option row in muted text, since SLDS has no "no results" row.
  • CommandDialog is a slds-modal with no header, footer or padding.

Known gaps:

  • SLDS centers its modal. CommandDialog opens a third of the way down, so it doesn't move as the results change.
  • An item with an icon starts its text where SLDS's listbox does, further in than a Dropdown menu item, which follows SLDS's menu. A command used as a menu won't line up with one.
  • No entity options with a meta line and object icons. Use Lookup for records.
PropValueSLDSNotes
Commanddefaultslds-dropdown (surface, no shadow), slds-listbox_verticalNo SLDS counterpart. Popover fill, radius-2 corners, 12px text. Add a shadow through className when it stands alone.
CommandInputdefaultslds-input-has-icon_left, slds-input__icon_leftInput group's search field, inset 12px, with the input focus ring.
CommandItemdefaultslds-listbox__option_plain slds-media_small34px rows. Active: menu fill, and SLDS's inset ring while focus is inside.
CommandItem data-checked / aria-checkedtrueslds-listbox__option.slds-is-selectedThe check in SLDS's 24px icon column, before the text. On an item with its own icon, the check goes at the end, as shadcn's does.
CommandGroup headingdefaultslds-listbox__option-header
CommandSeparatordefaultslds-has-divider_top-space
CommandDialogdefaultslds-modal (headless, footless)A third of the way down the screen, as shadcn places it.

Keyboard interactions

Keys Action
Typing Filters the items. The first match becomes active.
Down Arrow, Up Arrow Shows the active item, then moves to the next or previous one, skipping disabled ones.
Home, End, Cmd+Up Arrow, Cmd+Down Arrow Moves to the first or last item.
Alt+Down Arrow, Alt+Up Arrow Moves to the first item of the next or previous group.
Ctrl+N, Ctrl+J / Ctrl+P, Ctrl+K Moves down or up, as the arrows do. Turn off with vimBindings={false}.
Enter Selects the active item, shown or not, so typing then Enter runs the first match.
Escape In a CommandDialog or popover, closes it. Inline, does nothing.

A palette's shortcut, like Cmd+K, is yours to add.

Accessibility

  • Always pass label to Command. Inside a popover, also give PopoverContent an aria-label.
  • CommandDialog's title and description name the dialog for screen readers and aren't shown. Set them to what the palette searches.
  • Mark the current choice with aria-checked on every item: true on the chosen one, false on the rest, so their text lines up. Screen reader support for it on an option varies, so also show the choice on the button that opens the list.
  • Screen readers aren't told how many items match, or that nothing does. Keep a role="status" element outside the list and put the count or "No results" in it. An axe scan also flags the empty row inside the list.
  • CommandDialog returns focus on close to whatever had it when it opened, as long as that element is still on the page and can take focus.

Motion

  • CommandDialog's backdrop and box fade in and out as Dialog's do. Fades don't move anything, so they stay on with reduced motion.

Responsive behavior

  • Command fills its container's width. Give it a max-w-* on a wide page.
  • The list shows seven rows, then scrolls. Long item text wraps, and shortcuts stay at the end of the row.
  • CommandDialog is half the screen wide, between 20rem and 40rem, or the screen less 2rem a side on small screens. It stops 1rem above the bottom, and on a short screen its list scrolls inside it.

Performance

  • Every item renders and is scored on every keystroke. A few hundred items filter without trouble.
  • For thousands, or for results from a server, pass shouldFilter={false} to Command, control CommandInput's value, and render only the items you fetched.

Rules

  • CommandDialog doesn't add a Command. Put one inside it.
  • Use CommandGroup's heading for headers, not your own elements, and a CommandSeparator between groups.
  • Don't add your own check icon. aria-checked or data-checked on an item draws the check.
  • Inside a popover or dialog, add no shadow; they have their own. On its own on a page, give Command shadow-(--elevation-2), as the Search records and actions example does.
  • Keep a list to seven rows or fewer, headers included, before people have to scroll.