---
layout: ~/layouts/MdxDocLayout.astro
title: "CommandPalette"
description: "A composable command palette component for search and command dialogs."
sourceFile: "components/command-palette"
---

import ComponentExample from "~/components/docs/ComponentExample.astro";
import ComponentSection from "~/components/docs/ComponentSection.astro";
import CodeBlock from "~/components/docs/CodeBlock.astro";
import Heading from "~/components/docs/Heading.astro";
import PropsTable from "~/components/docs/PropsTable.astro";
import {
  CommandPaletteBasicDemo,
  CommandPaletteSimpleDemo,
  CommandPaletteLoadingDemo,
  CommandPaletteResultItemDemo,
} from "~/components/demos/CommandPaletteDemo";

{/* Demo */}

<ComponentSection>
  <ComponentExample
    code={`<CommandPalette.Root
  open={open}
  onOpenChange={setOpen}
  items={groups}
  value={search}
  onValueChange={setSearch}
>
  <CommandPalette.Input placeholder="Search..." />
  <CommandPalette.List>
    <CommandPalette.Results>
      {(group) => (
        <CommandPalette.Group>
          <CommandPalette.GroupLabel>{group.label}</CommandPalette.GroupLabel>
          <CommandPalette.Items>
            {(item) => (
              <CommandPalette.Item value={item}>
                {item.title}
              </CommandPalette.Item>
            )}
          </CommandPalette.Items>
        </CommandPalette.Group>
      )}
    </CommandPalette.Results>
    <CommandPalette.Empty>No results</CommandPalette.Empty>
  </CommandPalette.List>
  <CommandPalette.Footer>...</CommandPalette.Footer>
</CommandPalette.Root>`}
  >
    <CommandPaletteBasicDemo client:visible />
  </ComponentExample>
</ComponentSection>

{/* Installation */}

<ComponentSection>
  <Heading level={2}>Installation</Heading>
  <Heading level={3}>Barrel</Heading>
  <CodeBlock
    code={`import { CommandPalette } from "@cloudflare/kumo";`}
    lang="tsx"
  />
  <Heading level={3}>Granular</Heading>
  <CodeBlock
    code={`import { CommandPalette } from "@cloudflare/kumo/components/command-palette";`}
    lang="tsx"
  />
</ComponentSection>

{/* Usage */}

<ComponentSection>
  <Heading level={2}>Usage</Heading>
  <p>
    CommandPalette is a compound component built on Base UI's Autocomplete
    primitive. It provides accessible keyboard navigation and customizable
    styling for command palette interfaces.
  </p>
  <CodeBlock
    code={`import { useState } from "react";
import { CommandPalette } from "@cloudflare/kumo";

interface Item {
id: string;
title: string;
}

const items: Item[] = [
{ id: "1", title: "Create Project" },
{ id: "2", title: "Open Settings" },
];

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

return (

<>
  <button onClick={() => setOpen(true)}>Open</button>
  <CommandPalette.Root
    open={open}
    onOpenChange={setOpen}
    items={items}
    value={search}
    onValueChange={setSearch}
    itemToStringValue={(item) => item.title}
    getSelectableItems={(items) => items}
  >
    <CommandPalette.Input placeholder="Search..." />
    <CommandPalette.List>
      <CommandPalette.Results>
        {(item) => (
          <CommandPalette.Item
            key={item.id}
            value={item}
            onClick={() => setOpen(false)}
          >
            {item.title}
          </CommandPalette.Item>
        )}
      </CommandPalette.Results>
      <CommandPalette.Empty>No results</CommandPalette.Empty>
    </CommandPalette.List>
  </CommandPalette.Root>
</>
); }`} lang="tsx" />

</ComponentSection>

{/* Keyboard Navigation */}

<ComponentSection>
  <Heading level={2}>Keyboard Navigation</Heading>
  <p>Built-in keyboard navigation is provided automatically:</p>
  <ul class="mb-4 list-inside list-disc space-y-2 text-kumo-subtle">
    <li>
      <kbd class="rounded border border-kumo-line bg-kumo-base px-1.5 py-0.5 text-xs">
        ↑
      </kbd>
      <kbd class="rounded border border-kumo-line bg-kumo-base px-1.5 py-0.5 text-xs">
        ↓
      </kbd>
      - Move highlight between items
    </li>
    <li>
      <kbd class="rounded border border-kumo-line bg-kumo-base px-1.5 py-0.5 text-xs">
        Enter
      </kbd>
      - Select highlighted item
    </li>
    <li>
      <kbd class="rounded border border-kumo-line bg-kumo-base px-1.5 py-0.5 text-xs">
        ⌘/Ctrl + Enter
      </kbd>
      - Select with `newTab: true`
    </li>
    <li>
      <kbd class="rounded border border-kumo-line bg-kumo-base px-1.5 py-0.5 text-xs">
        Escape
      </kbd>
      - Close the dialog
    </li>
  </ul>
</ComponentSection>

{/* Examples */}

<ComponentSection>
  <Heading level={2}>Examples</Heading>

<Heading level={3}>With Grouped Items</Heading>
<p>Group related commands together with labels.</p>
<ComponentExample
  code={`<CommandPalette.Root items={groups} ...>
  <CommandPalette.Input placeholder="Type a command..." />
  <CommandPalette.List>
    <CommandPalette.Results>
      {(group) => (
        <CommandPalette.Group>
          <CommandPalette.GroupLabel>{group.label}</CommandPalette.GroupLabel>
          <CommandPalette.Items>
            {(item) => (
              <CommandPalette.Item value={item}>
                {item.title}
              </CommandPalette.Item>
            )}
          </CommandPalette.Items>
        </CommandPalette.Group>
      )}
    </CommandPalette.Results>
  </CommandPalette.List>
</CommandPalette.Root>`}
>
  <CommandPaletteBasicDemo client:visible />
</ComponentExample>

  <Heading level={3}>Simple Flat List</Heading>
  <p>
    For simpler use cases, use a flat array of items without grouping.
  </p>
  <ComponentExample
    code={`<CommandPalette.Root

items={items}
itemToStringValue={(item) => item.title}
getSelectableItems={(items) => items}

>

  <CommandPalette.Input placeholder="Search actions..." />
  <CommandPalette.List>
    <CommandPalette.Results>
      {(item) => (
        <CommandPalette.Item value={item}>
          {item.title}
        </CommandPalette.Item>
      )}
    </CommandPalette.Results>
  </CommandPalette.List>
</CommandPalette.Root>`}
  >
    <CommandPaletteSimpleDemo client:visible />
  </ComponentExample>

  <Heading level={3}>Loading State</Heading>
  <p>
    Show a loading spinner while fetching results.
  </p>
  <ComponentExample
    code={`<CommandPalette.List>

{loading ? (

<CommandPalette.Loading />
) : (
<CommandPalette.Results>
{(item) => ...}
</CommandPalette.Results>
)}

</CommandPalette.List>`}
  >
    <CommandPaletteLoadingDemo client:visible />
  </ComponentExample>

  <Heading level={3}>ResultItem with Breadcrumbs</Heading>
  <p>
    Use `ResultItem` for rich items with
    breadcrumbs, icons, and optional text highlighting.
  </p>
  <ComponentExample
    code={`<CommandPalette.ResultItem

value={item}
title={item.title}
breadcrumbs={["Components"]}
icon={<FileIcon size={16} />}
onClick={(e) => handleClick(e)}
/>`} >

<CommandPaletteResultItemDemo client:visible />
</ComponentExample>
</ComponentSection>

{/* Component Parts */}

<ComponentSection>
  <Heading level={2}>Component Parts</Heading>

<Heading level={3}>CommandPalette.Root</Heading>
<p>
  The main wrapper that combines Dialog + Panel. Manages open state and
  Autocomplete functionality.
</p>

<Heading level={3}>CommandPalette.Dialog</Heading>
<p>
  Modal dialog wrapper. Use with Panel for swappable content (e.g., drill-down
  navigation).
</p>

<Heading level={3}>CommandPalette.Panel</Heading>
<p>
  Autocomplete panel without dialog. Use inside Dialog for content that can swap
  without re-mounting.
</p>

<Heading level={3}>CommandPalette.Input</Heading>
<p>Search input field with auto-focus and keyboard handling.</p>

<Heading level={3}>CommandPalette.List</Heading>
<p>Scrollable container for results.</p>

<Heading level={3}>CommandPalette.Results</Heading>
<p>Render prop iterator for items/groups.</p>

<Heading level={3}>CommandPalette.Group</Heading>
<p>Category grouping container.</p>

<Heading level={3}>CommandPalette.GroupLabel</Heading>
<p>Section header text within a group.</p>

<Heading level={3}>CommandPalette.Items</Heading>
<p>Render prop iterator for items within a group.</p>

<Heading level={3}>CommandPalette.Item</Heading>
<p>Basic selectable item.</p>

<Heading level={3}>CommandPalette.ResultItem</Heading>
<p>Rich item with breadcrumbs, icons, and text highlighting.</p>

<Heading level={3}>CommandPalette.HighlightedText</Heading>
<p>Renders text with highlighted portions based on match indices.</p>

<Heading level={3}>CommandPalette.Empty</Heading>
<p>Empty state when no results match.</p>

<Heading level={3}>CommandPalette.Loading</Heading>
<p>Loading spinner state.</p>

  <Heading level={3}>CommandPalette.Footer</Heading>
  <p>
    Footer for keyboard hints or other content.
  </p>
</ComponentSection>

{/* API Reference */}

<ComponentSection>
  <Heading level={2}>API Reference</Heading>

  <Heading level={3}>CommandPalette.Root Props</Heading>
  <CodeBlock
    code={`interface CommandPaletteRootProps<TGroup, TItem> {
  // Dialog state
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onBackdropClick?: (e: React.MouseEvent) => void;

// Autocomplete
items: TGroup[];
value?: string;
onValueChange?: (value: string) => void;
itemToStringValue?: (item: TGroup) => string;
filter?: (item: TGroup, query: string) => boolean;
onItemHighlighted?: (item: TGroup | undefined, details: {...}) => void;

// Selection
onSelect?: (item: TItem, options: { newTab: boolean }) => void;
getSelectableItems?: (items: TGroup[]) => TItem[];

children: React.ReactNode;
}`}
lang="tsx"
/>

  <Heading level={3}>CommandPalette.ResultItem Props</Heading>
  <CodeBlock
    code={`interface CommandPaletteResultItemProps<T> {
  value: T;
  title: string;
  breadcrumbs?: string[];
  titleHighlights?: [number, number][];
  breadcrumbHighlights?: [number, number][][];
  description?: string;
  icon?: React.ReactNode;
  onClick: (e: React.MouseEvent) => void;
  showArrow?: boolean;  // default: true
  external?: boolean;   // shows external link icon
  nonInteractive?: boolean;
}`}
    lang="tsx"
  />
</ComponentSection>
