Command
A searchable list of commands; inside CommandDialog it is a Cmd+K palette.
- Picking a value for a form field: use Combobox.
Install
Terminal
pnpm dlx shadcn@latest add @acme/commandOr import from the package: import { Command } from '@acme/react'
Examples
Basic
command-basic.tsx
'use client'
import { FilePlus, Search, Settings, UserPlus } from 'lucide-react'
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import {
Command,
CommandCollection,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandShortcut,
} from '@/components/ui/command'
type Item = { value: string; label: string; icon: typeof Search; shortcut?: string }
type Group = { value: string; items: Item[] }
const groups: Group[] = [
{
value: 'Create',
items: [
{ value: 'new-invoice', label: 'New invoice', icon: FilePlus, shortcut: 'N' },
{ value: 'invite', label: 'Invite teammate', icon: UserPlus },
],
},
{
value: 'Go to',
items: [
{ value: 'search', label: 'Search customers', icon: Search },
{ value: 'settings', label: 'Settings', icon: Settings, shortcut: ',' },
],
},
]
export default function CommandBasic() {
const [open, setOpen] = useState(false)
// Cmd/Ctrl+K opens the palette.
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
setOpen((o) => !o)
}
}
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [])
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
Open command palette
<kbd className="ml-2 rounded-sm bg-muted px-1.5 font-mono text-caption">⌘K</kbd>
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<Command items={groups}>
<CommandInput aria-label="Search commands" placeholder="Type a command or search…" />
<CommandEmpty>No results.</CommandEmpty>
<CommandList>
{(group: Group) => (
<CommandGroup key={group.value} items={group.items} heading={group.value}>
<CommandCollection>
{(item: Item) => (
<CommandItem key={item.value} value={item} onClick={() => setOpen(false)}>
<item.icon aria-hidden="true" />
{item.label}
{item.shortcut ? <CommandShortcut>⌘{item.shortcut}</CommandShortcut> : null}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
)}
</CommandList>
</Command>
</CommandDialog>
</>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items (required) | T[] | Group[] | — | Commands, optionally grouped ({ value, items }). |
| CommandInput.aria-label (required) | string | — | Names the search field. |
| CommandGroup.heading | ReactNode | — | Group heading. |
| CommandItem.onClick | () => void | — | Runs the command. |
| CommandDialog.open / onOpenChange | boolean / fn | — | Controlled state. |
| CommandDialog.title | string | 'Command palette' | Names the dialog. |
Accessibility
| Key | Action |
|---|---|
| Cmd/Ctrl+K | Opens the palette (bind in your app) |
| Type | Filters commands |
| Arrow keys | Move highlight |
| Enter | Runs the highlighted command |
| Escape | Closes the palette |
- Role
- combobox + listbox (in a dialog)
- You provide
- aria-label on CommandInput (enforced by TypeScript).
- Built in
- The first match is always highlighted, so Enter works right after typing.
- WCAG 2.2 criteria
- 2.1.1, 2.4.3, 4.1.2
Don’t
- Make the palette the only way to reach a feature.
Tokens
- color.popover
- color.accent
- color.muted-foreground
- color.scrim
- elevation.3