# Command

Find and run actions in an inline list or a dialog.

Command palette

[Complete TSX file](/examples/command/default.tsx)

```tsx
"use client";

import {
  ArrowDownIcon,
  ArrowUpIcon,
  CornerDownLeftIcon,
  CreditCardIcon,
  FolderIcon,
  FolderPlusIcon,
  LayoutDashboardIcon,
  type LucideIcon,
  SettingsIcon,
  UserPlusIcon,
  UsersIcon,
} from "lucide-react";
import { Fragment, useEffect, useState } from "react";
import { Button } from "@/siteplane/components/button";
import {
  Command,
  CommandCollection,
  CommandDialog,
  CommandDialogPopup,
  CommandDialogTrigger,
  CommandEmpty,
  CommandFooter,
  CommandGroup,
  CommandGroupLabel,
  CommandInput,
  CommandItem,
  CommandList,
  CommandPanel,
  CommandSeparator,
  CommandShortcut,
} from "@/siteplane/components/command";
import { Kbd, KbdGroup } from "@/siteplane/components/kbd";

type CommandAction = { icon: LucideIcon; label: string; shortcut?: string };

const groups: { value: string; items: CommandAction[] }[] = [
  {
    value: "Pages",
    items: [
      { icon: LayoutDashboardIcon, label: "Dashboard" },
      { icon: FolderIcon, label: "Projects" },
      { icon: UsersIcon, label: "Members" },
      { icon: CreditCardIcon, label: "Billing" },
    ],
  },
  {
    value: "Actions",
    items: [
      { icon: FolderPlusIcon, label: "Create project", shortcut: "⌘N" },
      { icon: UserPlusIcon, label: "Invite member", shortcut: "⌘I" },
      { icon: SettingsIcon, label: "Open settings", shortcut: "⌘," },
    ],
  },
];

export default function Example() {
  const [open, setOpen] = useState(false);

  useEffect(() => {
    function onKeyDown(event: KeyboardEvent) {
      if (event.key === "j" && (event.metaKey || event.ctrlKey)) {
        event.preventDefault();
        setOpen((current) => !current);
      }
    }
    document.addEventListener("keydown", onKeyDown);
    return () => document.removeEventListener("keydown", onKeyDown);
  }, []);

  return (
    <CommandDialog onOpenChange={setOpen} open={open}>
      <CommandDialogTrigger render={<Button variant="outline" />}>
        Open command palette
        <KbdGroup>
          <Kbd>⌘</Kbd>
          <Kbd>J</Kbd>
        </KbdGroup>
      </CommandDialogTrigger>
      <CommandDialogPopup>
        <Command
          items={groups}
          itemToStringValue={(item: CommandAction) => item.label}
        >
          <CommandInput placeholder="Type a command or search…" />
          <CommandPanel>
            <CommandEmpty>No results found.</CommandEmpty>
            <CommandList>
              {(group: (typeof groups)[number], index: number) => (
                <Fragment key={group.value}>
                  <CommandGroup items={group.items}>
                    <CommandGroupLabel>{group.value}</CommandGroupLabel>
                    <CommandCollection>
                      {(item: CommandAction) => (
                        <CommandItem
                          key={item.label}
                          onClick={() => setOpen(false)}
                          value={item}
                        >
                          <item.icon />
                          <span className="flex-1">{item.label}</span>
                          {item.shortcut && (
                            <CommandShortcut>{item.shortcut}</CommandShortcut>
                          )}
                        </CommandItem>
                      )}
                    </CommandCollection>
                  </CommandGroup>
                  {index < groups.length - 1 && <CommandSeparator />}
                </Fragment>
              )}
            </CommandList>
          </CommandPanel>
          <CommandFooter>
            <div className="flex items-center gap-3">
              <div className="flex items-center gap-1.5">
                <KbdGroup>
                  <Kbd>
                    <ArrowUpIcon />
                  </Kbd>
                  <Kbd>
                    <ArrowDownIcon />
                  </Kbd>
                </KbdGroup>
                <span>Navigate</span>
              </div>
              <div className="flex items-center gap-1.5">
                <Kbd>
                  <CornerDownLeftIcon />
                </Kbd>
                <span>Open</span>
              </div>
            </div>
            <div className="flex items-center gap-1.5">
              <Kbd>Esc</Kbd>
              <span>Close</span>
            </div>
          </CommandFooter>
        </Command>
      </CommandDialogPopup>
    </CommandDialog>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/command
```

With npm: `npx shadcn@4.18.0 add @siteplane/command`. [Registry JSON](/registry/command.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/button @siteplane/kbd
```

## Usage

```tsx
import { SettingsIcon } from "lucide-react";
import {
  Command,
  CommandInput,
  CommandPanel,
  CommandList,
  CommandItem,
} from "@/siteplane/components/command";

<Command items={["Settings"]}>
  <CommandInput aria-label="Search" />
  <CommandPanel>
    <CommandList>
      {(item: string) => (
        <CommandItem value={item}>
          <SettingsIcon />
          {item}
        </CommandItem>
      )}
    </CommandList>
  </CommandPanel>
</Command>;
```

## Composition

```text
Command
├── CommandInput
├── CommandPanel
│   ├── CommandEmpty
│   └── CommandList → CommandItem
│       or CommandGroup → CommandGroupLabel + CommandCollection → CommandItem
└── CommandFooter

CommandDialog → CommandDialogTrigger + CommandDialogPopup → Command
```

## Inline command

`Command` renders an open, inline list by default, so it can sit in a page or panel without a dialog. `CommandShortcut` only displays a hint; register the matching keyboard handler in your application.

The search row and the results follow the [control size](/docs/customization/providers/#control-size): the row is 40, 44, 48 and 60 px tall and a result 37, 41, 45 and 56 px from `sm` to `xl`. A `size` on `CommandInput` sizes only its text.

Inline command with groups and shortcuts

[Complete TSX file](/examples/command/groups.tsx)

```tsx
"use client";

import {
  CreditCardIcon,
  FolderPlusIcon,
  SettingsIcon,
  UserIcon,
  UserPlusIcon,
} from "lucide-react";
import { Fragment } from "react";
import {
  Command,
  CommandCollection,
  CommandEmpty,
  CommandGroup,
  CommandGroupLabel,
  CommandInput,
  CommandItem,
  CommandList,
  CommandPanel,
  CommandSeparator,
  CommandShortcut,
} from "@/siteplane/components/command";

type CommandAction = {
  icon: typeof UserIcon;
  label: string;
  shortcut?: string;
};

const groups: { value: string; items: CommandAction[] }[] = [
  {
    value: "Suggestions",
    items: [
      { icon: FolderPlusIcon, label: "Create project" },
      { icon: UserPlusIcon, label: "Invite member" },
    ],
  },
  {
    value: "Settings",
    items: [
      { icon: UserIcon, label: "Profile", shortcut: "⌘P" },
      { icon: CreditCardIcon, label: "Billing", shortcut: "⌘B" },
      { icon: SettingsIcon, label: "Settings", shortcut: "⌘S" },
    ],
  },
];

export default function Example() {
  return (
    <div className="w-full max-w-md">
      <Command
        items={groups}
        itemToStringValue={(item: CommandAction) => item.label}
      >
        <CommandInput placeholder="Type a command or search…" />
        <CommandPanel>
          <CommandEmpty>No results found.</CommandEmpty>
          <CommandList>
            {(group: (typeof groups)[number], index: number) => (
              <Fragment key={group.value}>
                <CommandGroup items={group.items}>
                  <CommandGroupLabel>{group.value}</CommandGroupLabel>
                  <CommandCollection>
                    {(item: CommandAction) => (
                      <CommandItem key={item.label} value={item}>
                        <item.icon />
                        <span className="flex-1">{item.label}</span>
                        {item.shortcut && (
                          <CommandShortcut>{item.shortcut}</CommandShortcut>
                        )}
                      </CommandItem>
                    )}
                  </CommandCollection>
                </CommandGroup>
                {index < groups.length - 1 && <CommandSeparator />}
              </Fragment>
            )}
          </CommandList>
        </CommandPanel>
      </Command>
    </div>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| Command.items | readonly ItemValue[] | — | String/object items or grouped collections. |
| Command.autoHighlight | Autocomplete autoHighlight | always | Highlights a matching item automatically. |
| Command.keepHighlight | boolean | true | Keeps the highlight across pointer movement. |
| Command inline / open | Autocomplete root props | true / true | Inline, visible command list by default. |
| CommandInput | AutocompleteInput props | aria-label "Search commands", no placeholder | Search input; override aria-label to describe what it searches. |
| CommandPanel | div props | — | Scroll/layout container. |
| CommandList / CommandEmpty | AutocompleteList / AutocompleteEmpty props | — | Filtered render callback and empty result. |
| CommandGroup / CommandGroupLabel / CommandCollection | Matching Autocomplete part props | — | Grouped item rendering. |
| CommandItem | AutocompleteItem props | — | value, disabled, onClick and render. |
| CommandSeparator | AutocompleteSeparator props | — | Decorative division. |
| CommandShortcut / CommandFooter | kbd / div props | — | Shortcut presentation and supporting instructions. |
| CommandDialogPopup | DialogPopup props | aria-label "Command palette", no close button | Dialog popup sized for a command palette. |
| CommandDialog / CommandDialogTrigger / CommandDialogPortal / CommandDialogBackdrop / CommandDialogViewport | Corresponding Dialog parts | Dialog defaults | Aliases for dialog composition. |
| CommandCreateHandle / CommandDialogPrimitive | DialogCreateHandle / DialogPrimitive | — | Detached handle and foundation namespace. |

[Read the installed source](/source/command).

## Usage notes

Remaining root and item contracts follow [Autocomplete](/docs/components/autocomplete/#api-reference); dialog state, focus, portals and dismissal follow [Dialog](/docs/components/dialog/#api-reference). This component does not register global shortcuts, execute commands, persist history, or navigate automatically; the command palette example adds its own ⌘J/Ctrl+J handler.
