Qure UI
Components

Command

A command palette — type a few letters, run the thing. ⌘K.

Nothing run yet.

'use client'import * as React from 'react'import { CircleAlertIcon, FileTextIcon, LayersIcon, SendIcon, UserRoundIcon } from 'lucide-react'import { Button } from '@/registry/qure/ui/button'import {  CommandDialog, CommandEmpty, CommandInput, CommandItem, CommandList, CommandShortcut,} from '@/registry/qure/ui/command'type Action = { value: string; label: string; icon: React.ElementType; shortcut?: string }const actions: Action[] = [  { value: 'open-worklist', label: 'Open worklist', icon: LayersIcon, shortcut: 'G W' },  { value: 'new-report', label: 'Start a report', icon: FileTextIcon, shortcut: 'N' },  { value: 'find-patient', label: 'Find a patient', icon: UserRoundIcon, shortcut: '/' },  { value: 'flag-critical', label: 'Flag as critical', icon: CircleAlertIcon },  { value: 'send-report', label: 'Send report to referrer', icon: SendIcon, shortcut: 'S' },]export default function CommandDemo() {  const [open, setOpen] = React.useState(false)  const [ran, setRan] = React.useState<string | null>(null)  React.useEffect(() => {    function onKeyDown(event: KeyboardEvent) {      if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {        event.preventDefault()        setOpen(prev => !prev)      }    }    document.addEventListener('keydown', onKeyDown)    return () => document.removeEventListener('keydown', onKeyDown)  }, [])  return (    <div className="flex flex-col items-start gap-3">      <Button variant="secondary" onClick={() => setOpen(true)}>        Open palette <CommandShortcut className="ml-2">⌘K</CommandShortcut>      </Button>      <p className="text-muted-foreground text-sm">        {ran ? `Ran: ${ran}` : 'Nothing run yet.'}      </p>      <CommandDialog<Action> open={open} onOpenChange={setOpen} items={actions}>        <CommandInput placeholder="Type a command or search" />        {/* Empty is a sibling of the list, not a child: a list with a function            child has no room for anything else, and Empty has to stay mounted            to be announced. */}        <CommandEmpty>Nothing matches that.</CommandEmpty>        <CommandList>          {(action: Action) => (            <CommandItem              key={action.value}              value={action}              onClick={() => {                setRan(action.label)                setOpen(false)              }}            >              <action.icon className="size-4" />              {action.label}              {action.shortcut ? <CommandShortcut>{action.shortcut}</CommandShortcut> : null}            </CommandItem>          )}        </CommandList>      </CommandDialog>    </div>  )}

Installation

npx shadcn@latest add https://qure-ui.qure.ai/r/command.json

Usage

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

ACC-482913 — CT chest, Kaur R.
ACC-482910 — Chest X-ray, Prasad M.
ACC-482904 — CT head, Fernandes J.
ACC-482891 — Chest X-ray, Iyer S.
ACC-482874 — MRI brain, Banerjee T.

Nothing opened yet.

'use client'import * as React from 'react'import {  Command, CommandEmpty, CommandInput, CommandItem, CommandList,} from '@/registry/qure/ui/command'const studies = [  'ACC-482913 — CT chest, Kaur R.',  'ACC-482910 — Chest X-ray, Prasad M.',  'ACC-482904 — CT head, Fernandes J.',  'ACC-482891 — Chest X-ray, Iyer S.',  'ACC-482874 — MRI brain, Banerjee T.',]export default function CommandInlinePalette() {  const [picked, setPicked] = React.useState<string | null>(null)  return (    <div className="w-full max-w-md">      {/* No dialog. The palette is the panel — useful in a sidebar, or as the          body of a step in a wizard. */}      <Command items={studies} className="rounded-md border">        <CommandInput placeholder="Jump to a study" />        <CommandEmpty>No study matches that accession.</CommandEmpty>        <CommandList>          {(study: string) => (            <CommandItem key={study} value={study} onClick={() => setPicked(study)}>              {study}            </CommandItem>          )}        </CommandList>      </Command>      <p className="text-muted-foreground mt-3 text-sm">        {picked ? `Opened ${picked}` : 'Nothing opened yet.'}      </p>    </div>  )}

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

Report
Sign and finalise⌘ ⏎
Add an addendum
Revert to preliminary
Study
Print study⌘ P
Download DICOM
Share with referrer
'use client'import * as React from 'react'import { ArrowUpRightIcon, CheckIcon, DownloadIcon, PrinterIcon, RotateCcwIcon, ShareIcon } from 'lucide-react'import {  Command, CommandCollection, CommandEmpty, CommandGroup, CommandGroupLabel,  CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut,} from '@/registry/qure/ui/command'type Action = { value: string; label: string; icon: React.ElementType; shortcut?: string }const groups: { value: string; items: Action[] }[] = [  {    value: 'Report',    items: [      { value: 'sign', label: 'Sign and finalise', icon: CheckIcon, shortcut: '⌘ ⏎' },      { value: 'addendum', label: 'Add an addendum', icon: ArrowUpRightIcon },      { value: 'revert', label: 'Revert to preliminary', icon: RotateCcwIcon },    ],  },  {    value: 'Study',    items: [      { value: 'print', label: 'Print study', icon: PrinterIcon, shortcut: '⌘ P' },      { value: 'download', label: 'Download DICOM', icon: DownloadIcon },      { value: 'share', label: 'Share with referrer', icon: ShareIcon },    ],  },]export default function CommandGroupsExample() {  return (    <div className="w-full max-w-md">      <Command items={groups} className="rounded-md border">        <CommandInput placeholder="Filter actions" />        <CommandEmpty>No action matches that.</CommandEmpty>        <CommandList>          {(group: { value: string; items: Action[] }, index: number) => (            <React.Fragment key={group.value}>              {index > 0 ? <CommandSeparator /> : null}              <CommandGroup items={group.items}>                <CommandGroupLabel>{group.value}</CommandGroupLabel>                <CommandCollection>                  {(action: Action) => (                    <CommandItem key={action.value} value={action}>                      <action.icon className="size-4" />                      {action.label}                      {action.shortcut ? <CommandShortcut>{action.shortcut}</CommandShortcut> : null}                    </CommandItem>                  )}                </CommandCollection>              </CommandGroup>            </React.Fragment>          )}        </CommandList>      </Command>    </div>  )}

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

Chest X-ray, normal
CT chest, nodule follow-up
CT head, trauma
'use client'import * as React from 'react'import { Button } from '@/registry/qure/ui/button'import {  Command, CommandEmpty, CommandInput, CommandItem, CommandList,} from '@/registry/qure/ui/command'const templates = ['Chest X-ray, normal', 'CT chest, nodule follow-up', 'CT head, trauma']export default function CommandEmptyExample() {  const [query, setQuery] = React.useState('')  return (    <div className="w-full max-w-md">      <Command items={templates} onInputValueChange={setQuery} className="rounded-md border">        <CommandInput placeholder='Try "mri"' />        {/*          An empty state that only says "no results" wastes the one moment the          reader is definitely paying attention. Offer the next move.        */}        <CommandEmpty>          <p>No template called “{query}”.</p>          <Button variant="tertiary" size="sm" className="mt-3">            Create “{query}”          </Button>        </CommandEmpty>        <CommandList>          {(template: string) => (            <CommandItem key={template} value={template}>{template}</CommandItem>          )}        </CommandList>      </Command>    </div>  )}

"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.

On this page