<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Command

A command palette: a searchable list of every action in the app, in a dialog. Typing filters the commands by label and keywords (ignoring case and accents), the arrow keys move the highlight, and Enter runs the highlighted command. Commands come in labelled groups and show their shortcuts with `Kbd`; when nothing matches it says so. Below 640px it is a full-height bottom sheet, whose search input stays put while the results filter.

```tsx
import { Command } from "@/components/ouro/command/command";
```

## Example

```tsx
const [open, setOpen] = useCommandShortcut("mod+k");
<Command
  title="Command palette"
  open={open}
  onOpenChange={setOpen}
  items={[
    { label: "Recipes", items: [{ label: "New recipe", onSelect: create, shortcut: ["mod", "n"] }] },
    { label: "Switch theme", onSelect: toggleTheme, keywords: ["dark mode"] },
  ]}
/>
```

## Usage

Open it with `trigger`, or control it with `open` and `onOpenChange`, usually from `useCommandShortcut`, which opens it on ⌘K (Ctrl+K elsewhere): `import { useCommandShortcut } from "@/lib/ouro/use-command-shortcut"`.

`useCommandShortcut(shortcut = "mod+k")` returns `[open, setOpen]` and toggles `open` on the shortcut anywhere on the page. It takes the keys as `"mod+k"` or `["mod", "k"]`, with Kbd's names: `mod` (⌘ on Apple platforms, Ctrl elsewhere), `cmd`, `ctrl`, `alt` and `shift`. It needs exactly those modifiers, ignores a held key's repeats and prevents the browser's action for that combination. It never fires while an input method is composing text, and in a text field it fires only when the shortcut includes `mod`, `cmd` or `ctrl`, so a shortcut like `"/"` never takes a typed character.

## Use instead when

- Use `Menu` instead for the few actions on one thing, and `Combobox` to pick a value for a form.

## Props

The props the modes share. Type: `CommandProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  | Layout only, applied to the dialog panel: margins (`mt-8`). Appearance classes warn in development. |
| `closeLabel` | `string` | `"Close"` | The label of the close button, hidden until it has keyboard focus, so screen-reader users on phones (who have no Escape key) can always close the palette. |
| `emptyLabel` | `string` | `"No results"` | What shows when nothing matches the search. |
| `items` (required) | `readonly CommandEntry[]` |  | The commands: groups (`{ label: "Recipes", items: [...] }`), single commands, or both. |
| `loading` | `boolean` |  | Whether results are on their way: shows `loadingLabel` beside the input, announces it politely for the first load of each opening (not again while typing goes on), and holds back the empty state. Set it back to false when the request fails or aborts. |
| `loadingLabel` | `string` | `"Searching…"` | What shows, and is announced, while `loading`. |
| `onQueryChange` | `((query: string, context: { signal: AbortSignal }) => void)` |  | For results from a server: called with the search text on every change, and with the current text (`""` at each opening) whenever the palette opens or swaps between dialog and sheet. The palette then stops filtering `items` itself and shows them as they are, so keep the last results in `items` until new ones arrive. `signal` aborts when a newer query replaces this one or the palette closes: pass it to `fetch`, or drop the response when `signal.aborted`, so a slow earlier response never overwrites a newer one. The app owns the request, and must set `loading` back to false when the request fails or its signal aborts, or "Searching…" stays up. |
| `placeholder` | `string` | `"Type a command or search…"` | The search input's placeholder. |
| `searchLabel` | `string` | `"Search commands"` | The search input's label for screen readers. |
| `title` (required) | `string` |  | Names the palette for screen readers; it is not shown. It is always required. |

## Modes

`CommandProps` is a union: pass the props of exactly one mode. Mixing them is a type error.

### Controlled: `CommandControlledProps`

A controlled Command palette, usually opened by `useCommandShortcut`.

| Prop | Type | Description |
| --- | --- | --- |
| `onOpenChange` (required) | `(open: boolean) => void` | Called with `false` when the user closes the palette (Escape, outside press, swipe), and when a command runs. |
| `open` (required) | `boolean` | Whether the palette is open. After a key press it focuses the search input; after a touch or pen tap it focuses itself, so no software keyboard pops up. |

Not in this mode (typed `never`):

- `trigger`: Not with `open`. Remove `open` and `onOpenChange` to let a trigger open the palette.

### With trigger: `CommandWithTriggerProps`

An uncontrolled Command palette: `trigger` opens it.

| Prop | Type | Description |
| --- | --- | --- |
| `trigger` (required) | `ReactElement` | The element that opens the palette, usually a Button: `trigger={<Button>Search</Button>}`. |

Not in this mode (typed `never`):

- `onOpenChange`: Not with `trigger`. Remove `trigger` to control the palette with `open` and `onOpenChange`.
- `open`: Not with `trigger`. Remove `trigger` to control the palette with `open` and `onOpenChange`.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `children`: Not a prop. The commands are `items`: `items={[{ label: "New recipe", onSelect: create }]}`.
- `commands`: Not a prop. The commands are `items`: `items={[{ label: "New recipe", onSelect: create }]}`.
- `isOpen`: Not a prop. Ouro UI uses `open` with `onOpenChange`: `<Command open={open} onOpenChange={setOpen}>`.
- `onClose`: Not a prop. `onOpenChange(false)` fires on close: `<Command open={open} onOpenChange={setOpen}>`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`).

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `CommandEntry`

One entry in a Command palette's `items`: a command, or a group of them.

```ts
type CommandEntry = CommandItem | CommandGroup;
```

### `CommandGroup`

A labelled group of commands in a Command palette's `items`.

```ts
interface CommandGroup {
  /** The group's heading: "Recipes". */
  label: string;
  /** The group's commands. */
  items: readonly CommandItem[];
}
```

### `CommandItem`

One command in a Command palette's `items`.

```ts
interface CommandItem {
  /** The command's text, which the search matches. */
  label: string;
  /** Runs the command. The palette closes first. */
  onSelect: () => void;
  /**
   * More words the search matches, which never show: `keywords: ["dark mode"]`.
   */
  keywords?: readonly string[] | undefined;
  /** The keys of the command's shortcut, shown at its end with `Kbd`: `["mod", "n"]`. It does not bind them. */
  shortcut?: readonly string[] | undefined;
  /** Listed, but cannot be run. */
  disabled?: boolean | undefined;

  /** Not a prop. A command runs `onSelect`: `{ label: "New recipe", onSelect: create }`. (A `MenuItem` is the one that takes `onClick`.) */
  onClick?: { 'Use onSelect: { label: "New recipe", onSelect: create }': never };
}
```
