# Command

> Keyboard-first command palette / filterable menu. Compound API with case-insensitive substring filter, groups, and full a11y (combobox/listbox/option).

- Available in: `@sisyphos-ui/react`, `@sisyphos-ui/vue`, `@sisyphos-ui/angular`
- Docs: https://www.sisyphosui.com/docs/components/command
- WAI-ARIA pattern: [Combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/)

## Installation

Pick the framework binding that matches your stack:

```bash
pnpm add @sisyphos-ui/react   # React 18+
pnpm add @sisyphos-ui/vue     # Vue 3+
pnpm add @sisyphos-ui/angular # Angular 17+
```

## Import

```tsx
import "@sisyphos-ui/react/styles.css";
import { Command } from "@sisyphos-ui/react";
```

## Framework usage

### React 18+

```tsx
import { Command } from "@sisyphos-ui/react";

export const Palette = () => (
  <Command label="Command menu">
    <Command.Input placeholder="Type a command…" />
    <Command.List>
      <Command.Empty>No results.</Command.Empty>
      <Command.Group heading="Suggestions">
        <Command.Item value="new-file" onSelect={() => {}}>New file</Command.Item>
        <Command.Item value="open-recent" onSelect={() => {}}>Open recent…</Command.Item>
      </Command.Group>
    </Command.List>
  </Command>
);
```

### Vue 3+

```vue
<script setup lang="ts">
import {
  Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem,
} from "@sisyphos-ui/vue";
</script>

<template>
  <Command label="Command menu">
    <CommandInput placeholder="Type a command…" />
    <CommandList>
      <CommandEmpty>No results.</CommandEmpty>
      <CommandGroup heading="Suggestions">
        <CommandItem value="new-file" @select="() => {}">New file</CommandItem>
        <CommandItem value="open-recent" @select="() => {}">Open recent…</CommandItem>
      </CommandGroup>
    </CommandList>
  </Command>
</template>
```

### Angular 17+

```ts
import { Component } from "@angular/core";
import {
  Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem,
} from "@sisyphos-ui/angular";

@Component({
  selector: "app-palette",
  standalone: true,
  imports: [Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem],
  template: `
    <sui-command label="Command menu">
      <sui-command-input placeholder="Type a command…" />
      <sui-command-list>
        <sui-command-empty>No results.</sui-command-empty>
        <sui-command-group heading="Suggestions">
          <sui-command-item value="new-file">New file</sui-command-item>
          <sui-command-item value="open-recent">Open recent…</sui-command-item>
        </sui-command-group>
      </sui-command-list>
    </sui-command>
  `,
})
export class PaletteComponent {}
```

## Examples

### Default

Type to filter. Arrow keys move the selection; Enter runs the active item.

```tsx
import { Command, toast } from "@sisyphos-ui/react";

export function Example() {
  return (
    <Command onSelect={(v) => toast.success(`Ran ${v}`)}>
      <Command.Input placeholder="Type a command or search…" />
      <Command.List>
        <Command.Empty>No results.</Command.Empty>
        <Command.Group heading="Suggestions">
          <Command.Item value="calendar">Calendar</Command.Item>
          <Command.Item value="emojis">Search Emojis</Command.Item>
          <Command.Item value="calculator">Calculator</Command.Item>
        </Command.Group>
        <Command.Separator />
        <Command.Group heading="Settings">
          <Command.Item value="profile">Profile</Command.Item>
          <Command.Item value="billing">Billing</Command.Item>
          <Command.Item value="preferences">Preferences</Command.Item>
        </Command.Group>
      </Command.List>
    </Command>
  );
}
```

### Inside a Dialog

The classic ⌘K experience — drop `<Command>` inside `<Dialog>` and the input takes focus on open.

```tsx
import { useState } from "react";
import { Button, Command, Dialog, toast } from "@sisyphos-ui/react";

export function Example() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <Button onClick={() => setOpen(true)}>Open command menu</Button>
      <Dialog open={open} onOpenChange={setOpen} size="sm">
        <Command
          onSelect={(v) => {
            toast.success(`Ran ${v}`);
            setOpen(false);
          }}
        >
          <Command.Input placeholder="Type a command or search…" />
          <Command.List>
            <Command.Empty>No results.</Command.Empty>
            <Command.Group heading="Navigate">
              <Command.Item value="dashboard">Go to Dashboard</Command.Item>
              <Command.Item value="projects">Go to Projects</Command.Item>
            </Command.Group>
          </Command.List>
        </Command>
      </Dialog>
    </>
  );
}
```

### With shortcuts

Drop a `<Kbd>` inside each `<Command.Item>` — it flows to the right automatically via `margin-left: auto` on the flex row.

```tsx
import { Command, Kbd } from "@sisyphos-ui/react";

export function Example() {
  return (
    <Command>
      <Command.Input placeholder="Search…" />
      <Command.List>
        <Command.Group heading="File">
          <Command.Item value="new">
            <span className="flex-1">New file</span>
            <Kbd size="xs" shortcut="cmd+n" />
          </Command.Item>
          <Command.Item value="open">
            <span className="flex-1">Open…</span>
            <Kbd size="xs" shortcut="cmd+o" />
          </Command.Item>
          <Command.Item value="save">
            <span className="flex-1">Save</span>
            <Kbd size="xs" shortcut="cmd+s" />
          </Command.Item>
        </Command.Group>
      </Command.List>
    </Command>
  );
}
```

### Disabled items

`disabled` items are still rendered but don't match, aren't focusable, and can't be activated.

```tsx
import { Command } from "@sisyphos-ui/react";

export function Example() {
  return (
    <Command>
      <Command.Input />
      <Command.List>
        <Command.Item value="edit">Edit</Command.Item>
        <Command.Item value="copy" disabled>Copy (disabled)</Command.Item>
        <Command.Item value="paste">Paste</Command.Item>
      </Command.List>
    </Command>
  );
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| value | `string` | — | Controlled search value. Omit to let the component manage its own state. |
| defaultValue | `string` | `""` | Initial search value when uncontrolled. |
| onValueChange | `(value: string) => void` | — | Fires whenever the search value changes. |
| onSelect | `(value: string) => void` | — | Fires when an item is activated (click or Enter). Receives the item's `value` prop. |
| label | `string` | `"Command menu"` | Accessible name for the combobox root. |

## Anatomy

```tsx
<Command.Root>
  <Command.Input /> // Text input that drives the filter. (required)
  <Command.List /> // Scrollable listbox region. (required)
  <Command.Empty /> // Rendered when no items match.
  <Command.Group /> // Optional section with an `heading`.
  <Command.Item /> // Selectable row filtered by `value`. (required)
  <Command.Separator /> // Visual divider.
</Command.Root>
```

## Keyboard interactions

- **ArrowDown + ArrowUp** — Move the active item (wraps at the edges).
- **Home + End** — First / last matching item.
- **Enter** — Activate the current item.

<!-- exports: { "Command": "@sisyphos-ui/react" } -->