# Command

A fast, keyboard-navigable command palette with search filtering, item grouping, and modal dialog support.

```slint
import { Command, CommandItem } from "@glint/components/command.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 560px;
    height: 360px;
    background: Tokens.color-background;

    in-out property <string> query: "";
    in-out property <string> chosen: "None";

    VerticalLayout {
        alignment: center;
        spacing: 12px;
        padding: 20px;

        HorizontalLayout {
            alignment: center;

            Command {
                query <=> root.query;
                items: [
                    { id: "new-file", label: "New File", icon: IconSet.FilePlus, hint: "Ctrl+N" },
                    { id: "open", label: "Open File…", icon: IconSet.FolderOpen, hint: "Ctrl+O" },
                    { id: "docs", label: "Documentation", icon: IconSet.BookOpen, separator-before: true, heading-before: "Help" },
                    { id: "settings", label: "Settings", icon: IconSet.Settings, hint: "Ctrl+," },
                ];
                selected(id) => {
                    root.chosen = id;
                }
            }
        }

        Text {
            text: "Executed command: " + root.chosen;
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
        }
    }
}
```

## Usage

```slint
import { Command, CommandItem } from "@glint/components/command.slint";

export component AppWindow inherits Window {
    in-out property <string> search-text;

    VerticalLayout {
        alignment: center;

        Command {
            query <=> root.search-text;
            items: [
                { id: "settings", label: "Settings", hint: "Preferences" },
                { id: "profile", label: "Profile", hint: "Account" },
            ];
            selected(id) => {
                // handle command
            }
        }
    }
}
```

`Command` provides an inline searchable palette holding a text input over a scrollable list of command options. The search query is two-way (`query <=> ...`), allowing the host application to filter `items` dynamically.

Pressing `↑`/`↓` navigates the highlight, `Home`/`End` jumps to list ends, `Enter` runs the selected command firing `selected(id)`, and `Escape` fires `dismissed()`.

For full-window modal invocation (such as `⌘K` / `Ctrl+K`), use `CommandDialog`.

## Examples

### Modal command palette with CommandDialog

`CommandDialog` wraps the command palette in a full-window modal overlay complete with backdrop scrim, focus trap (`FocusSentinel`), and accessible focus restoration (`restore-focus`).

```slint
import { Button } from "@glint/components/button.slint";
import { CommandDialog } from "@glint/components/command.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 560px;
    height: 360px;
    background: Tokens.color-background;

    in-out property <string> status: "Press button or open palette";

    VerticalLayout {
        alignment: center;
        spacing: 16px;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            trigger := Button {
                text: "Open Command Palette (Ctrl+K)";
                haspopup: true;
                clicked => { cmd.open = true; }
            }
        }

        Text {
            text: root.status;
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
        }
    }

    cmd := CommandDialog {
        width: parent.width;
        height: parent.height;
        placeholder: "Type a command…";
        items: [
            { id: "new-file", label: "Create New Project", icon: IconSet.FolderPlus },
            { id: "git-pull", label: "Git: Pull Latest Changes", icon: IconSet.GitPullRequest, separator-before: true, heading-before: "Version Control" },
            { id: "git-push", label: "Git: Push Commits", icon: IconSet.UploadCloud },
            { id: "help", label: "Keyboard Shortcuts Reference", icon: IconSet.HelpCircle, separator-before: true, heading-before: "General" },
        ];
        selected(id) => {
            root.status = "Ran command: " + id;
        }
        restore-focus => {
            trigger.focus();
        }
    }
}
```

### Grouped commands with hints and icons

Following Glint’s [grouped rows](/docs/components/select#grouped-rows) model, commands can be organized into categories using `separator-before` and `heading-before` without distorting command indexing. Trailing text hints provide category badges or shortcut keys.

```slint
import { Command } from "@glint/components/command.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 560px;
    height: 360px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 20px;

        HorizontalLayout {
            alignment: center;

            Command {
                items: [
                    { id: "calc", label: "Calculator", icon: IconSet.Calculator, hint: "App" },
                    { id: "calendar", label: "Calendar", icon: IconSet.Calendar, hint: "App" },
                    { id: "dark-theme", label: "Toggle Dark Theme", icon: IconSet.Moon, separator-before: true, heading-before: "Appearance", hint: "System" },
                    { id: "high-contrast", label: "High Contrast Mode", icon: IconSet.SunMedium, hint: "Accessibility" },
                ];
            }
        }
    }
}
```

### Zero results empty state

When filtering produces an empty array of items, `Command` displays an `Empty` state carrying `empty-text` rather than collapsing into a blank surface.

```slint
import { Command } from "@glint/components/command.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 280px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 20px;

        HorizontalLayout {
            alignment: center;

            Command {
                query: "xyznonexistent";
                items: [];
                empty-text: "No commands match your search.";
            }
        }
    }
}
```

## API Reference

### Properties

| Property            | Type               | Default                            | Description                                                                                                                                                                                                         |
| ------------------- | ------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`             | `in [CommandItem]` | no default                         | Commands shown in the palette. Each `id` is what `selected` fires with.                                                                                                                                             |
| `query`             | `in-out string`    | no default                         | Two-way; current search text.                                                                                                                                                                                       |
| `highlighted-index` | `in-out int`       | `0`                                | Row that Enter runs; consumers rarely set this directly.                                                                                                                                                            |
| `placeholder`       | `in string`        | `@tr("Type a command or search…")` | Hint shown in the search field when empty.                                                                                                                                                                          |
| `empty-text`        | `in string`        | `@tr("No results found.")`         | Shown in place of the list when `items` is empty — the state a search palette lands in most often. Set it to "" to show nothing at all.                                                                             |
| `max-visible-items` | `in int`           | `8`                                | The list shows at most this many rows before it starts scrolling.                                                                                                                                                   |
| `auto-focus`        | `in bool`          | `false`                            | Whether the search field takes the keyboard as soon as the palette is built. Off inline, where the palette is one thing on a page among many; `CommandDialog` turns it on for a palette that opens with the window. |

### Callbacks

| Callback           | Description                                                                                                                            |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `selected(string)` | Fired with the chosen command's id.                                                                                                    |
| `dismissed()`      | Fired on Escape. The inline surface reports it rather than acting on it: what a dismissal means belongs to whatever hosts the palette. |

### Functions

| Function          | Description                                                                                                                        |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `take-focus()`    | Puts the keyboard in the search field.                                                                                             |
| `release-focus()` | Gives it up again. Taking it first is what makes this work from anywhere: `clear-focus()` only speaks for the scope that holds it. |

### CommandDialog

The full-window modal version of the command palette with backdrop scrim, keyboard trap, and accessible landmark regions.

### Properties

| Property              | Type               | Default                            | Description                                                                                                                                                                                                     |
| --------------------- | ------------------ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `open`                | `in-out bool`      | `false`                            | Two-way; consumer sets true to open, Glint sets false on dismissal.                                                                                                                                             |
| `items`               | `in [CommandItem]` | no default                         | Commands shown in the palette. Each `id` is what `selected` fires with.                                                                                                                                         |
| `query`               | `in-out string`    | no default                         | Two-way; current search text.                                                                                                                                                                                   |
| `highlighted-index`   | `in-out int`       | `0`                                | Row that Enter runs; consumers rarely set this directly.                                                                                                                                                        |
| `placeholder`         | `in string`        | `@tr("Type a command or search…")` | Hint shown in the search field when empty.                                                                                                                                                                      |
| `empty-text`          | `in string`        | `@tr("No results found.")`         | Shown in place of the list when `items` is empty.                                                                                                                                                               |
| `max-visible-items`   | `in int`           | `8`                                | The list shows at most this many rows before it starts scrolling.                                                                                                                                               |
| `label`               | `in string`        | `@tr("Command palette")`           | What the open palette announces itself as, and an optional second line. Slint has no dialog role, so the card claims the closest landmark it has — a region — exactly as `DialogPanel` does, and this names it. |
| `description`         | `in string`        | no default                         |                                                                                                                                                                                                                 |
| `dismiss-on-backdrop` | `in bool`          | `true`                             | When true, clicking outside the card dismisses the palette.                                                                                                                                                     |
| `panel-width`         | `in length`        | `540px`                            | Width of the card.                                                                                                                                                                                              |

### Callbacks

| Callback           | Description                                                                                                                                                                                                                                                                                                               |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `selected(string)` | Fired with the chosen command's id. The palette closes itself.                                                                                                                                                                                                                                                            |
| `dismissed()`      | Fired when the palette closes itself — the backdrop or Escape. Running a command is not a dismissal: `selected` is the report for that.                                                                                                                                                                                   |
| `restore-focus()`  | Fired on EVERY close, including one the consumer drives by setting `open = false`, so keyboard focus always gets a new home: `restore-focus => { my-button.focus(); }`. Slint exposes no "previously focused element", so only the trigger's owner can name it. The same contract `Dialog`, `Sheet` and `Drawer` publish. |

## Accessibility

- **Landmark and role.** `Command` exposes an accessible list structure containing individual list items with index information. `CommandDialog` claims an accessible `region` landmark named by `label` and described by `description`.
- **Keyboard focus trap.** `CommandDialog` bounds keyboard navigation within the dialog using `FocusSentinel` markers, ensuring focus cannot wander outside the modal.
- **Focus restoration.** Dismissing `CommandDialog` invokes `restore-focus`, allowing applications to hand keyboard focus back to the originating button.
- **Group headings.** Category headings are presented as non-focusable informative labels.
- **Keyboard navigation.** Navigation keys (`↑`/`↓`, `Home`/`End`) are captured by the ancestor focus scope before reaching the text input, giving navigation priority over caret positioning.
