# Command

A filterable list of actions — the ⌘K surface.

- Group: Overlays
- Import: `import { Command } from '@misoto22/folio'`
- Page: https://ui.misoto22.com/components/command/
- Related: combobox, dialog

## Anatomy

- **Input** (required) — CommandInput, the combobox, with the search glyph and a hairline under it. A hard 52px tall rather than a --control-h token, so it does not move with data-density.
- **List** (required) — CommandList, capped at 18rem inline and raised to 26rem inside CommandDialog, scrolling on its own six-pixel hairline bar.
- **Item** — A row: an icon ELEMENT, the label, an optional meta note and a shortcut printed as a Kbd. The highlighted row takes --accent-muted and a leading accent rule, because a fill alone is hard to catch while scrolling.
- **Empty** — CommandEmpty. cmdk renders an empty state only if one exists in the tree, so without it an unmatched filter leaves the input over a blank strip.
- **Footer** — CommandFooter and its CommandHint rows. A palette is a keyboard surface whose keys are invisible; this is the only place they get printed.

## Best practices

### Do

- Reach for CommandDialog, not the bare Command. The root is an inline bordered box with no scrim, no focus trap and no Escape handling of its own, so a palette built from it alone stays open until something else closes it.
- Always render CommandEmpty. cmdk shows an empty state only when one is present, so the palette that omits it answers a filter matching nothing with an input above a blank strip and no explanation.
- Pass CommandItem an icon element — icon={<Search size={16} />} — when the glyph needs its own size or colour, and the component — icon={Search} — when it does not. Both spellings work here and in DropdownMenuItem; they used to be the reverse of each other one import apart.
- Bind ⌘K yourself and then print it: nothing in this component listens for a key, so the palette has no shortcut until the app adds a keydown handler, and a CommandFooter is where the reader finds out it exists.

### Don’t

- Do not pass value to CommandItem unless the value is what a reader would type — cmdk filters on value first and only falls back to the row’s own text when there is none, so an id passed as the value makes the visible label unsearchable. Pass the label’s words as keywords when the id has to stay the value, which is what SearchableMenu does.
- CommandDialog renders with hideTitle and showClose={false}, so the palette has no visible heading and no visible close: Escape and the scrim are the only exits and neither announces itself. Print Escape in a CommandFooter rather than assuming it is known.
- Do not expect the palette to follow data-density — CommandInput is a hard 52px, not a --control-h token, so its field stays put while every other control in the app shrinks under compact.

## Accessibility

- The list filters as you type, the highlight moves with the arrow keys, and focus stays in the input. That last part is the ARIA combobox pattern and the part a home-made palette gets wrong.

## Keyboard

- ↑ / ↓ — Moves the highlight. Focus stays in the input, so what you typed stays editable.
- Enter — Runs the highlighted item.
- Escape — Closes the palette.

## Command

A filterable list of actions — the ⌘K surface. Built on cmdk rather than on a `Select` or a menu, because the interaction is neither: the list is filtered as you type, the highlighted row moves with the arrow keys while focus STAYS in the input, and Enter runs the highlighted row. That is the ARIA combobox pattern, and it is the part nobody should hand-roll — `aria-activedescendant` moving without focus moving is precisely where a home-made palette stops working with a screen reader.

## CommandInput

The filter field. Carries the search icon and the combobox semantics.

## CommandList

## CommandEmpty

Shown when the filter matches nothing. Say what would match, not "no results".

## CommandFooter

The key-hint strip along the bottom. A palette is a keyboard surface whose keys are invisible: nothing on screen says the arrows move the row or that Enter runs it, and a reader who reaches for the mouse has been failed by the design rather than by themselves.

## CommandHint

One key-and-verb pair inside a `CommandFooter`.

### Props

- `keys` (required) — `string[]`. The keys this hint describes, printed as `Kbd` chips.
- `children` (required) — `ReactNode`. What they do — a verb, lowercase, no sentence.

Also accepts: `Omit<ComponentProps<'span'>, 'children'>`.

## CommandGroup

## CommandSeparator

A divider between groups. Marked presentational, because ARIA permits a `listbox` to contain only `option` and `group` — and the library renders this as `role="separator"`, which puts a critical `aria-required-children` violation inside every palette that uses one. The grouping is already announced by the groups themselves, so removing the divider from the accessibility tree loses nothing.

## CommandItem

### Props

- `shortcut` — `string`. A shortcut printed at the end of the row.
- `icon` — `RemixiconComponentType | ReactNode`. A leading glyph. Either spelling — `icon={<Settings size={16} />}` passes the element, `icon={Settings}` passes the component and this sizes it. It is what makes a long list scannable — the eye sorts by shape before it reads, and forty identical rows of text defeat that.
- `meta` — `ReactNode`. A quiet note at the end of the row — what kind of thing this is, or its current state. Not a description: a palette that prints a sentence per row stops being scannable at about six of them.

Also accepts: `ComponentProps<typeof CommandPrimitive.Item>`.

## CommandDialog

The palette in a modal, which is how it is nearly always used. The dialog's own padding is removed: a palette is edge-to-edge, and its input is the first thing focus lands on.

### Props

- `open` (required) — `boolean`.
- `onOpenChange` (required) — `(open: boolean) => void`.
- `label` (required) — `string`. Names the palette for assistive tech.
- `inputLabel` — `string`. Names the search field when it should read differently from the dialog — a "Search records" field inside a "Library search" palette. Defaults to `label`. An `aria-label` on `CommandInput` cannot do this: the field is labelled by reference, and a reference outranks an `aria-label`.
- `children` (required) — `ReactNode`.
- `shouldFilter` — `boolean`. Pass `false` when the host has already filtered the rows — a server search or an index of its own. The palette would otherwise filter them a second time against the input and hide results the host meant to show.
- `onOpenAutoFocus` — `DialogContentProps['onOpenAutoFocus']`. Runs as the dialog moves focus in. Prevent the default to place focus yourself.
- `onCloseAutoFocus` — `DialogContentProps['onCloseAutoFocus']`. Runs as the dialog hands focus back on close. The default returns it to a `DialogTrigger`, and a palette opened by a shortcut has none, so focus falls to the page. Prevent the default and focus what opened it.
- `onEscapeKeyDown` — `DialogContentProps['onEscapeKeyDown']`. Runs before Escape closes the dialog. Prevent the default to keep it open, as a nested view stepping back does.

## Example — inline

```tsx
<Command label="Command palette" className="w-full max-w-md">
  <CommandInput placeholder="Type a command or search…" />
  <CommandList>
    <CommandEmpty>No command matches that. Try “theme”.</CommandEmpty>
    {/* The glyph is what makes a long list scannable: the eye sorts by
        shape before it reads. */}
    <CommandGroup heading="Navigate">
      <CommandItem value="components" icon={<RiShapesLine aria-hidden />} meta="49" shortcut="C">
        Components
      </CommandItem>
      <CommandItem value="foundations" icon={<RiRulerLine aria-hidden />} shortcut="F">
        Foundations
      </CommandItem>
      <CommandItem value="principles" icon={<RiScales3Line aria-hidden />} shortcut="P">
        Principles
      </CommandItem>
      <CommandItem value="templates" icon={<RiCompassLine aria-hidden />}>
        Templates
      </CommandItem>
    </CommandGroup>
    <CommandSeparator />
    <CommandGroup heading="Theme">
      <CommandItem value="light" icon={<RiSunLine aria-hidden />} meta="current">
        Switch to light
      </CommandItem>
      <CommandItem value="dark" icon={<RiMoonLine aria-hidden />}>
        Switch to dark
      </CommandItem>
    </CommandGroup>
  </CommandList>
  {/* Nothing on screen otherwise says the arrows move the row or that
      Enter runs it. */}
  <CommandFooter>
    <CommandHint keys={['↑', '↓']}>navigate</CommandHint>
    <CommandHint keys={['↵']}>run</CommandHint>
    <CommandHint keys={['esc']}>close</CommandHint>
  </CommandFooter>
</Command>
```

## Example — dialog

```tsx
) => document.removeEventListener('keydown', onKey)
  }, [])

  return (
<div className="flex flex-col items-center gap-3">
  <Button variant="secondary" onClick={() => setOpen(true)}>
    Open the palette
  </Button>
  <p className="m-0 text-sm text-(--ink-3-aa)">
    or press <Kbd>⌘</Kbd> <Kbd>K</Kbd>
  </p>
  <CommandDialog open={open} onOpenChange={setOpen} label="Command palette">
    <CommandInput placeholder="Type a command…" />
    <CommandList>
      <CommandEmpty>No command matches that.</CommandEmpty>
      <CommandGroup heading="Navigate">
        <CommandItem value="components" onSelect={() => setOpen(false)}>Components</CommandItem>
        <CommandItem value="principles" onSelect={() => setOpen(false)}>Principles</CommandItem>
      </CommandGroup>
    </CommandList>
  </CommandDialog>
</div>
```

## Example — values and keywords

```tsx
<Command label="Project commands" className="w-full max-w-md">
  <CommandInput placeholder="Try revert, or teammate…" />
  <CommandList>
    <CommandEmpty>Nothing matches. Try branch, export or revert.</CommandEmpty>
    <CommandGroup heading="Release">
      <CommandItem
        value="roll back the release"
        keywords={['revert', 'undo', 'previous']}
        icon={<RiResetLeftLine size={16} aria-hidden />}
        meta="release"
      >
        Roll back the release
      </CommandItem>
      <CommandItem
        value="create a release branch"
        keywords={['cut', 'branch', 'tag']}
        icon={<RiGitBranchLine size={16} aria-hidden />}
        meta="git"
      >
        Create a release branch
      </CommandItem>
    </CommandGroup>
    <CommandGroup heading="Project">
      <CommandItem
        value="invite a collaborator"
        keywords={['teammate', 'member', 'people']}
        icon={<RiGroupLine size={16} aria-hidden />}
        meta="people"
      >
        Invite a collaborator
      </CommandItem>
      <CommandItem
        value="export the deploy log"
        keywords={['csv', 'download', 'spreadsheet']}
        icon={<RiFileDownloadLine size={16} aria-hidden />}
        meta="data"
      >
        Export the deploy log
      </CommandItem>
    </CommandGroup>
  </CommandList>
</Command>
```
