Command
A command palette — type a few letters, run the thing. ⌘K.
Nothing run yet.
Installation
npx shadcn@latest add https://qure-ui.qure.ai/r/command.jsonUsage
import {
Command, CommandDialog, CommandEmpty, CommandGroup, CommandGroupLabel,
CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut,
} from '@/components/ui/command'const actions = [
{ value: 'sign', label: 'Sign and finalise' },
{ value: 'print', label: 'Print study' },
]
<CommandDialog open={open} onOpenChange={setOpen} items={actions}>
<CommandInput placeholder="Type a command or search" />
<CommandEmpty>Nothing matches that.</CommandEmpty>
<CommandList>
{(action) => (
<CommandItem key={action.value} value={action} onClick={run(action)}>
{action.label}
</CommandItem>
)}
</CommandList>
</CommandDialog>A palette is for the reader who already knows what they want and does not want to go and find the button. It is an accelerator, never the only route: every action in it has to exist somewhere a first-time reader can see it. A palette that is the only way to sign a report is a palette that loses reports.
What it is built on
cmdk is the usual implementation and we do not use it. Base UI's Combobox is the same machine — a
text input that filters a list and owns a highlight — and it has an inline mode that renders
the list in place instead of in a popup. That is a palette.
The gain is not fewer bytes. It is that the keyboard model, the ARIA and the filtering are the ones every other list in this library already uses, so a highlighted palette row and a highlighted combobox row behave and look the same because they are the same code.
inline requires the Combobox's open to be set unconditionally, so Command fixes it rather
than exposing it. In CommandDialog the Combobox and the Dialog share one open: Base UI
clears the query, the highlight and the input value when the combobox closes, so the palette
never reopens showing last time's search.
Composition
<CommandDialog> {/* Combobox root + Dialog, one shared `open` */}
<CommandInput> {/* the filter; the search icon is rendered for you */}
<CommandEmpty> {/* sibling of the list, not a child */}
<CommandList> {/* function child over `items` */}
<CommandSeparator>
<CommandGroup>
<CommandGroupLabel>
<CommandCollection>
<CommandItem>
<CommandShortcut>
</CommandDialog>CommandEmpty is a sibling of CommandList, never inside it — a list with a function child has
no room for anything else, and Empty is a live region that must stay mounted to be announced.
Conditionally render the text inside it, not the element.
The dialog's title and description are rendered visually hidden. They are still read when the palette opens, which is the only thing telling a screen-reader user what just took their focus.
Inline, without a dialog
Nothing opened yet.
The palette is just a panel. It works in a sidebar, in a popover, or as one step of a wizard — anywhere a filtered list of actions is the content rather than an interruption.
Groups and shortcuts
Group when the actions divide by object — what this does to the report, what it does to the
study. CommandShortcut is a plain span rather than a <kbd> per key: it is a reminder of a
shortcut that exists elsewhere, not markup asking the reader to press something now.
An empty state worth reading
"No results" wastes the one moment the reader is definitely looking at the panel. If there is a next move — create it, search elsewhere, clear a filter — put it here.
Keyboard
Observed: ⌘K is yours to wire up, and the demo above does it with a keydown listener
on the document. Once open, typing filters; ArrowDown and ArrowUp move the
highlight and loop; Enter fires the highlighted item's onClick; Escape
closes the dialog. Focus lands on the input when it opens and returns to the trigger when it
closes.
API Reference
Command and CommandDialog take Base UI Combobox root
props, minus the ones they fix. What is ours:
Prop
Type
inline and open are not accepted on Command — it is always an open inline list, and that is
what makes it render in place.