Skip to content

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/command

Or 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

Command props
PropTypeDefaultDescription
items (required)T[] | Group[]—Commands, optionally grouped ({ value, items }).
CommandInput.aria-label (required)string—Names the search field.
CommandGroup.headingReactNode—Group heading.
CommandItem.onClick() => void—Runs the command.
CommandDialog.open / onOpenChangeboolean / fn—Controlled state.
CommandDialog.titlestring'Command palette'Names the dialog.

Accessibility

Keyboard
KeyAction
Cmd/Ctrl+KOpens the palette (bind in your app)
TypeFilters commands
Arrow keysMove highlight
EnterRuns the highlighted command
EscapeCloses 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