# Command

A searchable list of actions, on the page or in a dialog.

```tsx
import { Fragment } from "react"
import * as stylex from "@stylexjs/stylex"
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from "@/components/ui/command"
import { radius } from "@/theme/tokens.stylex"
import { Calculator, Calendar, CreditCard, Settings, Smile, User } from "lucide-react"
import type { LucideIcon } from "lucide-react"

type Action = {
  value: string
  label: string
  icon: LucideIcon
  shortcut?: string
  disabled?: boolean
}
type Group = { value: string; items: Action[] }

const groups: Group[] = [
  {
    value: "Suggestions",
    items: [
      { value: "calendar", label: "Calendar", icon: Calendar },
      { value: "emoji", label: "Search Emoji", icon: Smile },
      { value: "calculator", label: "Calculator", icon: Calculator, disabled: true },
    ],
  },
  {
    value: "Settings",
    items: [
      { value: "profile", label: "Profile", icon: User, shortcut: "⌘P" },
      { value: "billing", label: "Billing", icon: CreditCard, shortcut: "⌘B" },
      { value: "settings", label: "Settings", icon: Settings, shortcut: "⌘S" },
    ],
  },
]

const styles = stylex.create({
  command: { maxWidth: "24rem", borderRadius: radius.lg, borderWidth: 1, borderStyle: "solid" },
})

export default function CommandDemo() {
  return (
    <Command items={groups} xstyle={styles.command}>
      <CommandInput placeholder="Type a command or search..." aria-label="Search commands" />
      <CommandEmpty>No results found.</CommandEmpty>
      <CommandList>
        {(group: Group, index: number) => (
          <Fragment key={group.value}>
            {index > 0 && <CommandSeparator />}
            <CommandGroup heading={group.value} items={group.items}>
              {(item: Action) => (
                <CommandItem key={item.value} value={item} disabled={item.disabled}>
                  <item.icon />
                  <span>{item.label}</span>
                  {item.shortcut && <CommandShortcut>{item.shortcut}</CommandShortcut>}
                </CommandItem>
              )}
            </CommandGroup>
          </Fragment>
        )}
      </CommandList>
    </Command>
  )
}
```

## About

`Command` is Base UI's `Autocomplete` rendered inline, not the `cmdk` package
that shadcn's Command is built on. The list is always open, the input filters
it, and an item runs its `onClick` when clicked or when Enter is pressed while
it is highlighted. The trade-off is the data shape. Options go through `items`
and render from a function child, the way a `Combobox` does.

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex clsx lucide-react
   ```

2. Set up the token layer and the StyleX compiler, if you have not already. See
   [Installation](https://carbon.style/docs/installation), and paste `states.ts` and `state-styles.ts`
   from the same page. `states.ts` holds the dimmed item style, and an item reads
   its highlight and disabled state through `state-styles.ts`. The list needs `interop.css`
   for its hidden scrollbar and for the icons it does not render.

3. Add [Input](https://carbon.style/docs/components/input), [Input Group](https://carbon.style/docs/components/input-group)
   and [Dialog](https://carbon.style/docs/components/dialog). The search field is an `InputGroup`, and
   `CommandDialog` is a `Dialog`.

4. Copy and paste the following code into your project.

   ```tsx title="components/ui/command.tsx"
   "use client"

   import { useRender } from "@base-ui/react/use-render"
   import type { ComponentProps, CSSProperties, ReactNode } from "react"
   import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"
   import { clsx } from "clsx"
   import { Search } from "lucide-react"

   import { Dialog, DialogContent, DialogDescription, DialogTitle } from "@/components/ui/dialog"
   import { Input } from "@/components/ui/input"
   import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"
   import { stateStyles } from "@/lib/state-styles"
   import { states } from "@/theme/states"
   import { color, leading, radius, space, text, tracking } from "@/theme/tokens.stylex"

   const styles = stylex.create({
     root: {
       display: "flex",
       width: "100%",
       height: "100%",
       flexDirection: "column",
       overflow: "hidden",
       "--carbon-command-radius": radius.xl,
       borderRadius: "var(--carbon-command-radius)",
       padding: space.s1,
       backgroundColor: color.popover,
       color: color.popoverForeground,
     },
     inputWrapper: {
       marginInline: `calc(-1 * ${space.s1})`,
       marginBlockStart: `calc(-1 * ${space.s1})`,
       paddingInline: space.s1,
       borderBlockEndWidth: 1,
       borderBlockEndStyle: "solid",
       borderBlockEndColor: color.border,
     },
     inputFrame: {
       minHeight: space.s10,
       borderColor: color.transparent,
       backgroundColor: color.transparent,
       boxShadow: "none",
     },
     list: {
       maxHeight: "18rem",
       overflowX: "hidden",
       overflowY: "auto",
       overscrollBehavior: "contain",
       scrollPaddingTop: space.s1,
       scrollPaddingBottom: space.s1,
       outlineStyle: "none",
     },
     empty: {
       paddingBlock: { default: space.s6, ":empty": 0 },
       textAlign: "center",
       fontSize: text.sm,
       lineHeight: leading.sm,
     },
     group: { overflow: "hidden", padding: space.s1, color: color.foreground },
     heading: {
       paddingInline: space.s2,
       paddingBlock: space.s1_5,
       fontSize: text.xs,
       lineHeight: leading.xs,
       fontWeight: 500,
       color: color.mutedForeground,
     },
     separator: {
       marginInline: `calc(-1 * ${space.s1})`,
       height: 1,
       backgroundColor: color.border,
     },
     item: {
       position: "relative",
       display: "flex",
       alignItems: "center",
       gap: space.s2,
       cursor: "default",
       userSelect: "none",
       borderRadius: `max(0px, calc(var(--carbon-command-radius, ${radius.xl}) - 2 * ${space.s1}))`,
       paddingInline: space.s2,
       paddingBlock: space.s1_5,
       fontSize: text.sm,
       lineHeight: leading.sm,
       outlineStyle: "none",
       "--carbon-icon-size": space.s4,
     },
     shortcut: {
       marginInlineStart: "auto",
       fontSize: text.xs,
       lineHeight: leading.xs,
       letterSpacing: tracking.widest,
       color: color.mutedForeground,
     },
     dialogContent: {
       overflow: "hidden",
       padding: 0,
     },
     dialogViewport: { alignItems: "start", paddingBlockStart: "33vh" },
   })

   const commandStyles = {
     root: styles.root,
     list: styles.list,
     empty: styles.empty,
     group: styles.group,
     heading: styles.heading,
     separator: styles.separator,
     item: styles.item,
     itemHighlighted: states.itemHighlight,
     itemDisabled: states.disabled,
     shortcut: styles.shortcut,
   }

   type Overrides = { xstyle?: StyleXStyles; className?: string }

   type StateOverrides = Overrides & { style?: CSSProperties }

   type CommandProps<Item> = Omit<
     AutocompletePrimitive.Root.Props<Item>,
     | "inline"
     | "open"
     | "defaultOpen"
     | "onOpenChange"
     | "onOpenChangeComplete"
     | "openOnInputClick"
     | "modal"
     | "items"
   > &
     Overrides & {
       render?: useRender.ComponentProps<"div">["render"]
       items: NonNullable<AutocompletePrimitive.Root.Props<Item>["items"]>
     }

   function Command<Item>({
     render,
     xstyle,
     className,
     children,
     items,
     autoHighlight = "always",
     keepHighlight = true,
     ...props
   }: CommandProps<Item>) {
     const sx = stylex.props(styles.root, xstyle)

     return useRender({
       defaultTagName: "div",
       render,
       props: {
         "data-slot": "command",
         ...sx,
         className: clsx(sx.className, className),
         children: (
           <AutocompletePrimitive.Root
             inline
             open
             autoHighlight={autoHighlight}
             keepHighlight={keepHighlight}
             {...props}
             items={items as readonly Item[]}
           >
             {children}
           </AutocompletePrimitive.Root>
         ),
       },
     })
   }

   type CommandDialogProps = Omit<ComponentProps<typeof Dialog>, "children"> &
     Overrides & {
       title?: string
       description?: string
       showCloseButton?: boolean
       children: ReactNode
     }

   function CommandDialog({
     title = "Command Palette",
     description = "Search for a command to run...",
     children,
     xstyle,
     className,
     showCloseButton = false,
     ...props
   }: CommandDialogProps) {
     return (
       <Dialog {...props}>
         <DialogContent
           xstyle={[styles.dialogContent, xstyle]}
           className={className}
           showCloseButton={showCloseButton}
           viewportProps={{ xstyle: styles.dialogViewport }}
         >
           <DialogTitle xstyle={states.srOnly}>{title}</DialogTitle>
           <DialogDescription xstyle={states.srOnly}>{description}</DialogDescription>
           {children}
         </DialogContent>
       </Dialog>
     )
   }

   type CommandInputProps = Omit<AutocompletePrimitive.Input.Props, "className"> & Overrides

   function CommandInput({ xstyle, className, ...props }: CommandInputProps) {
     return (
       <div data-slot="command-input-wrapper" {...stylex.props(styles.inputWrapper)}>
         <InputGroup xstyle={[styles.inputFrame, xstyle]} className={className}>
           <AutocompletePrimitive.Input render={<Input />} {...props} />
           <InputGroupAddon>
             <Search />
           </InputGroupAddon>
         </InputGroup>
       </div>
     )
   }

   type CommandListProps = Omit<AutocompletePrimitive.List.Props, "className"> & Overrides

   function CommandList({ xstyle, className, ...props }: CommandListProps) {
     const sx = stylex.props(styles.list, xstyle)
     return (
       <AutocompletePrimitive.List
         data-slot="command-list"
         {...sx}
         {...props}
         className={clsx("no-scrollbar", sx.className, className)}
       />
     )
   }

   type CommandEmptyProps = Omit<AutocompletePrimitive.Empty.Props, "className"> & Overrides

   function CommandEmpty({ xstyle, className, ...props }: CommandEmptyProps) {
     const sx = stylex.props(styles.empty, xstyle)
     return (
       <AutocompletePrimitive.Empty
         data-slot="command-empty"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   type CommandGroupProps<Item> = Omit<
     AutocompletePrimitive.Group.Props,
     "className" | "children" | "items"
   > &
     Overrides & {
       heading?: ReactNode
       items?: readonly Item[]
       children?: ReactNode | ((item: Item, index: number) => ReactNode)
     }

   function CommandGroup<Item>({
     heading,
     items,
     xstyle,
     className,
     children,
     ...props
   }: CommandGroupProps<Item>) {
     const sx = stylex.props(styles.group, xstyle)
     return (
       <AutocompletePrimitive.Group
         data-slot="command-group"
         items={items}
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       >
         {heading != null && (
           <AutocompletePrimitive.GroupLabel
             data-slot="command-group-heading"
             {...stylex.props(styles.heading)}
           >
             {heading}
           </AutocompletePrimitive.GroupLabel>
         )}
         {typeof children === "function" ? (
           <AutocompletePrimitive.Collection>{children}</AutocompletePrimitive.Collection>
         ) : (
           children
         )}
       </AutocompletePrimitive.Group>
     )
   }

   type CommandSeparatorProps = Omit<AutocompletePrimitive.Separator.Props, "className"> & Overrides

   function CommandSeparator({ xstyle, className, ...props }: CommandSeparatorProps) {
     const sx = stylex.props(styles.separator, xstyle)
     return (
       <AutocompletePrimitive.Separator
         data-slot="command-separator"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   type CommandItemProps = Omit<AutocompletePrimitive.Item.Props, "className" | "style"> &
     StateOverrides

   function CommandItem({ xstyle, style, className, ...props }: CommandItemProps) {
     return (
       <AutocompletePrimitive.Item
         data-slot="command-item"
         {...props}
         {...stateStyles(
           (state) => [
             styles.item,
             state.highlighted && states.itemHighlight,
             state.disabled && states.disabled,
             xstyle,
           ],
           { className, style, hook: "carbon-icons" },
         )}
       />
     )
   }

   function CommandShortcut({
     render,
     ref,
     style,
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"span"> & Overrides) {
     const sx = stylex.props(styles.shortcut, xstyle)
     return useRender({
       defaultTagName: "span",
       render,
       ref,
       props: {
         "data-slot": "command-shortcut",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   export {
     Command,
     CommandDialog,
     CommandEmpty,
     CommandGroup,
     CommandInput,
     CommandItem,
     CommandList,
     CommandSeparator,
     CommandShortcut,
     commandStyles,
   }
   ```

5. Update the import paths to match your project setup.

## Usage

```tsx
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from "@/components/ui/command"
```

```tsx
const actions = [
  { value: "calendar", label: "Calendar" },
  { value: "emoji", label: "Search Emoji" },
  { value: "calculator", label: "Calculator" },
]

export function Example() {
  return (
    <Command items={actions}>
      <CommandInput placeholder="Type a command or search..." aria-label="Search commands" />
      <CommandEmpty>No results found.</CommandEmpty>
      <CommandList>
        <CommandGroup heading="Suggestions" items={actions}>
          {(item: (typeof actions)[number]) => (
            <CommandItem key={item.value} value={item} onClick={() => run(item)}>
              {item.label}
            </CommandItem>
          )}
        </CommandGroup>
      </CommandList>
    </Command>
  )
}
```

Items shaped `{ value, label }` filter on `label`. For any other shape, pass
`itemToStringValue` to `Command`.

## Composition

```text
Command
├── CommandInput
├── CommandEmpty
└── CommandList
    ├── CommandGroup
    │   └── CommandItem
    ├── CommandSeparator
    └── CommandGroup
        └── CommandItem
```

`CommandEmpty` sits before `CommandList`, not inside it. The list is a
listbox, and a status message is not one of its options.

## Groups

Pass groups shaped `{ value, items }` to `items`. `CommandList` takes a
function child that receives each group; render a `CommandGroup` with the
group's `items` and a function child that receives each item.

```tsx
"use client"

import { Fragment, useState } from "react"
import { Button } from "@/components/ui/button"
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from "@/components/ui/command"
import { Calculator, Calendar, CreditCard, Settings, Smile, User } from "lucide-react"
import type { LucideIcon } from "lucide-react"

type Action = { value: string; label: string; icon: LucideIcon; shortcut?: string }
type Group = { value: string; items: Action[] }

const groups: Group[] = [
  {
    value: "Suggestions",
    items: [
      { value: "calendar", label: "Calendar", icon: Calendar },
      { value: "emoji", label: "Search Emoji", icon: Smile },
      { value: "calculator", label: "Calculator", icon: Calculator },
    ],
  },
  {
    value: "Settings",
    items: [
      { value: "profile", label: "Profile", icon: User, shortcut: "⌘P" },
      { value: "billing", label: "Billing", icon: CreditCard, shortcut: "⌘B" },
      { value: "settings", label: "Settings", icon: Settings, shortcut: "⌘S" },
    ],
  },
]

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

  return (
    <>
      <Button variant="outline" onClick={() => setOpen(true)}>
        Open Menu
      </Button>
      <CommandDialog open={open} onOpenChange={setOpen}>
        <Command items={groups}>
          <CommandInput placeholder="Type a command or search..." aria-label="Search commands" />
          <CommandEmpty>No results found.</CommandEmpty>
          <CommandList>
            {(group: Group, index: number) => (
              <Fragment key={group.value}>
                {index > 0 && <CommandSeparator />}
                <CommandGroup heading={group.value} items={group.items}>
                  {(item: Action) => (
                    <CommandItem key={item.value} value={item} onClick={() => setOpen(false)}>
                      <item.icon />
                      <span>{item.label}</span>
                      {item.shortcut && <CommandShortcut>{item.shortcut}</CommandShortcut>}
                    </CommandItem>
                  )}
                </CommandGroup>
              </Fragment>
            )}
          </CommandList>
        </Command>
      </CommandDialog>
    </>
  )
}
```

Filtering drops a group with no match left, so put the `CommandSeparator`
before every group but the first rather than between groups.

## Basic

Put the `Command` inside a `CommandDialog` and control it with `open` and
`onOpenChange`. Close it from an item's `onClick`.

```tsx
"use client"

import { useState } from "react"
import { Button } from "@/components/ui/button"
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command"

type Action = { value: string; label: string }

const suggestions: Action[] = [
  { value: "calendar", label: "Calendar" },
  { value: "emoji", label: "Search Emoji" },
  { value: "calculator", label: "Calculator" },
]

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

  return (
    <>
      <Button variant="outline" onClick={() => setOpen(true)}>
        Open Menu
      </Button>
      <CommandDialog open={open} onOpenChange={setOpen}>
        <Command items={suggestions}>
          <CommandInput placeholder="Type a command or search..." aria-label="Search commands" />
          <CommandEmpty>No results found.</CommandEmpty>
          <CommandList>
            <CommandGroup heading="Suggestions" items={suggestions}>
              {(item: Action) => (
                <CommandItem key={item.value} value={item} onClick={() => setOpen(false)}>
                  {item.label}
                </CommandItem>
              )}
            </CommandGroup>
          </CommandList>
        </Command>
      </CommandDialog>
    </>
  )
}
```

## Dialog

Toggle the dialog from a keyboard shortcut. Nothing in the component binds one.

```tsx
"use client"

import { Fragment, useEffect, useState } from "react"
import * as stylex from "@stylexjs/stylex"
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from "@/components/ui/command"
import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { color, leading, text } from "@/theme/tokens.stylex"
import { Calculator, Calendar, CreditCard, Settings, Smile, User } from "lucide-react"
import type { LucideIcon } from "lucide-react"

type Action = { value: string; label: string; icon: LucideIcon; shortcut?: string }
type Group = { value: string; items: Action[] }

const groups: Group[] = [
  {
    value: "Suggestions",
    items: [
      { value: "calendar", label: "Calendar", icon: Calendar },
      { value: "emoji", label: "Search Emoji", icon: Smile },
      { value: "calculator", label: "Calculator", icon: Calculator },
    ],
  },
  {
    value: "Settings",
    items: [
      { value: "profile", label: "Profile", icon: User, shortcut: "⌘P" },
      { value: "billing", label: "Billing", icon: CreditCard, shortcut: "⌘B" },
      { value: "settings", label: "Settings", icon: Settings, shortcut: "⌘S" },
    ],
  },
]

const styles = stylex.create({
  hint: { color: color.mutedForeground, fontSize: text.sm, lineHeight: leading.sm },
})

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

  useEffect(() => {
    const down = (event: KeyboardEvent) => {
      if (event.key === "j" && (event.metaKey || event.ctrlKey)) {
        event.preventDefault()
        setOpen((open) => !open)
      }
    }

    document.addEventListener("keydown", down)
    return () => document.removeEventListener("keydown", down)
  }, [])

  return (
    <>
      <p {...stylex.props(styles.hint)}>
        Press{" "}
        <KbdGroup>
          <Kbd>⌘</Kbd>
          <Kbd>J</Kbd>
        </KbdGroup>
      </p>
      <CommandDialog open={open} onOpenChange={setOpen}>
        <Command items={groups}>
          <CommandInput placeholder="Type a command or search..." aria-label="Search commands" />
          <CommandEmpty>No results found.</CommandEmpty>
          <CommandList>
            {(group: Group, index: number) => (
              <Fragment key={group.value}>
                {index > 0 && <CommandSeparator />}
                <CommandGroup heading={group.value} items={group.items}>
                  {(item: Action) => (
                    <CommandItem key={item.value} value={item} onClick={() => setOpen(false)}>
                      <item.icon />
                      <span>{item.label}</span>
                      {item.shortcut && <CommandShortcut>{item.shortcut}</CommandShortcut>}
                    </CommandItem>
                  )}
                </CommandGroup>
              </Fragment>
            )}
          </CommandList>
        </Command>
      </CommandDialog>
    </>
  )
}
```

## Shortcuts

Use `CommandShortcut` to show a key combination at the trailing edge of an
item. It displays the key only.

```tsx
"use client"

import { useState } from "react"
import { Button } from "@/components/ui/button"
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandShortcut,
} from "@/components/ui/command"
import { CreditCard, Settings, User } from "lucide-react"
import type { LucideIcon } from "lucide-react"

type Action = { value: string; label: string; icon: LucideIcon; shortcut: string }

const settings: Action[] = [
  { value: "profile", label: "Profile", icon: User, shortcut: "⌘P" },
  { value: "billing", label: "Billing", icon: CreditCard, shortcut: "⌘B" },
  { value: "settings", label: "Settings", icon: Settings, shortcut: "⌘S" },
]

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

  return (
    <>
      <Button variant="outline" onClick={() => setOpen(true)}>
        Open Menu
      </Button>
      <CommandDialog open={open} onOpenChange={setOpen}>
        <Command items={settings}>
          <CommandInput placeholder="Type a command or search..." aria-label="Search commands" />
          <CommandEmpty>No results found.</CommandEmpty>
          <CommandList>
            <CommandGroup heading="Settings" items={settings}>
              {(item: Action) => (
                <CommandItem key={item.value} value={item} onClick={() => setOpen(false)}>
                  <item.icon />
                  <span>{item.label}</span>
                  <CommandShortcut>{item.shortcut}</CommandShortcut>
                </CommandItem>
              )}
            </CommandGroup>
          </CommandList>
        </Command>
      </CommandDialog>
    </>
  )
}
```

## Scrollable

The list stops at `18rem` and scrolls. The highlighted item scrolls into view.

```tsx
"use client"

import { Fragment, useState } from "react"
import { Button } from "@/components/ui/button"
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from "@/components/ui/command"
import {
  Bell,
  Calculator,
  Calendar,
  ClipboardPaste,
  Code,
  Copy,
  CreditCard,
  FileText,
  Folder,
  FolderPlus,
  HelpCircle,
  Home,
  Image,
  Inbox,
  LayoutGrid,
  List,
  Plus,
  Scissors,
  Settings,
  Trash,
  User,
  ZoomIn,
  ZoomOut,
} from "lucide-react"
import type { LucideIcon } from "lucide-react"

type Action = { value: string; label: string; icon: LucideIcon; shortcut?: string }
type Group = { value: string; items: Action[] }

const groups: Group[] = [
  {
    value: "Navigation",
    items: [
      { value: "home", label: "Home", icon: Home, shortcut: "⌘H" },
      { value: "inbox", label: "Inbox", icon: Inbox, shortcut: "⌘I" },
      { value: "documents", label: "Documents", icon: FileText, shortcut: "⌘D" },
      { value: "folders", label: "Folders", icon: Folder, shortcut: "⌘F" },
    ],
  },
  {
    value: "Actions",
    items: [
      { value: "new-file", label: "New File", icon: Plus, shortcut: "⌘N" },
      { value: "new-folder", label: "New Folder", icon: FolderPlus, shortcut: "⇧⌘N" },
      { value: "copy", label: "Copy", icon: Copy, shortcut: "⌘C" },
      { value: "cut", label: "Cut", icon: Scissors, shortcut: "⌘X" },
      { value: "paste", label: "Paste", icon: ClipboardPaste, shortcut: "⌘V" },
      { value: "delete", label: "Delete", icon: Trash, shortcut: "⌫" },
    ],
  },
  {
    value: "View",
    items: [
      { value: "grid", label: "Grid View", icon: LayoutGrid },
      { value: "list", label: "List View", icon: List },
      { value: "zoom-in", label: "Zoom In", icon: ZoomIn, shortcut: "⌘+" },
      { value: "zoom-out", label: "Zoom Out", icon: ZoomOut, shortcut: "⌘-" },
    ],
  },
  {
    value: "Account",
    items: [
      { value: "profile", label: "Profile", icon: User, shortcut: "⌘P" },
      { value: "billing", label: "Billing", icon: CreditCard, shortcut: "⌘B" },
      { value: "settings", label: "Settings", icon: Settings, shortcut: "⌘S" },
      { value: "notifications", label: "Notifications", icon: Bell },
      { value: "help", label: "Help & Support", icon: HelpCircle },
    ],
  },
  {
    value: "Tools",
    items: [
      { value: "calculator", label: "Calculator", icon: Calculator },
      { value: "calendar", label: "Calendar", icon: Calendar },
      { value: "image-editor", label: "Image Editor", icon: Image },
      { value: "code-editor", label: "Code Editor", icon: Code },
    ],
  },
]

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

  return (
    <>
      <Button variant="outline" onClick={() => setOpen(true)}>
        Open Menu
      </Button>
      <CommandDialog open={open} onOpenChange={setOpen}>
        <Command items={groups}>
          <CommandInput placeholder="Type a command or search..." aria-label="Search commands" />
          <CommandEmpty>No results found.</CommandEmpty>
          <CommandList>
            {(group: Group, index: number) => (
              <Fragment key={group.value}>
                {index > 0 && <CommandSeparator />}
                <CommandGroup heading={group.value} items={group.items}>
                  {(item: Action) => (
                    <CommandItem key={item.value} value={item} onClick={() => setOpen(false)}>
                      <item.icon />
                      <span>{item.label}</span>
                      {item.shortcut && <CommandShortcut>{item.shortcut}</CommandShortcut>}
                    </CommandItem>
                  )}
                </CommandGroup>
              </Fragment>
            )}
          </CommandList>
        </Command>
      </CommandDialog>
    </>
  )
}
```

## API reference

Pass `render` to replace a part's element or compose it with another component.

Every part wraps the Base UI `Autocomplete` part of the same name and accepts
its props. See the [Base UI reference](https://base-ui.com/react/components/autocomplete)
for the full list. Each styled part also takes `xstyle` and `className`.
`CommandItem` also takes `style`, which is real inline CSS merged over the style
its Base UI state resolves to.

### Command

| Prop                | Type                                  | Default    |
| ------------------- | ------------------------------------- | ---------- |
| `items`             | `readonly Item[] \| readonly Group[]` | —          |
| `autoHighlight`     | `boolean \| "always"`                 | `"always"` |
| `keepHighlight`     | `boolean`                             | `true`     |
| `itemToStringValue` | `(item: Item) => string`              | —          |
| `filter`            | `(item, query, toString) => boolean`  | —          |
| `value`             | `string`                              | —          |
| `onValueChange`     | `(value: string, details) => void`    | —          |

`value` is the input's text. `inline` and `open` are set and cannot be
changed.

### CommandDialog

| Prop              | Type                               | Default                            |
| ----------------- | ---------------------------------- | ---------------------------------- |
| `open`            | `boolean`                          | —                                  |
| `onOpenChange`    | `(open: boolean, details) => void` | —                                  |
| `title`           | `string`                           | `"Command Palette"`                |
| `description`     | `string`                           | `"Search for a command to run..."` |
| `showCloseButton` | `boolean`                          | `false`                            |

`title` and `description` are read to screen readers and not shown. The popup
sits a third of the way down the viewport and stays put as the list changes
height.

### CommandGroup

| Prop       | Type                                                    | Default |
| ---------- | ------------------------------------------------------- | ------- |
| `heading`  | `ReactNode`                                             | —       |
| `items`    | `readonly Item[]`                                       | —       |
| `children` | `ReactNode \| (item: Item, index: number) => ReactNode` | —       |

### CommandItem

| Prop       | Type              | Default |
| ---------- | ----------------- | ------- |
| `value`    | `Item`            | —       |
| `onClick`  | `(event) => void` | —       |
| `disabled` | `boolean`         | `false` |

### commandStyles

The style maps behind the component. Spread them onto any element.

| Key               | What it is                          |
| ----------------- | ----------------------------------- |
| `root`            | The panel: surface, radius, padding |
| `list`            | The scrolling list                  |
| `empty`           | The empty message                   |
| `group`           | A group's padding                   |
| `heading`         | A group heading                     |
| `separator`       | The hairline between groups         |
| `item`            | An item's row and type              |
| `itemHighlighted` | The highlight fill and inset ring   |
| `itemDisabled`    | The dimmed, inert item              |
| `shortcut`        | `CommandShortcut`                   |

`itemDisabled` is `disabled` from `states` in `theme/states.ts`, the same dim
every other control uses. `root` keeps its own fill. The panel is inline, so it
takes no popup ring or shadow.

### Data attributes

| Attribute                           | On         | Effect                            |
| ----------------------------------- | ---------- | --------------------------------- |
| `data-slot="command"`               | the panel  | Targets the panel from a parent   |
| `data-slot="input-group"`           | the field  | The frame `CommandInput` renders  |
| `data-slot="command-list"`          | the list   | Targets the list from a parent    |
| `data-slot="command-group"`         | a group    | Targets a group from a parent     |
| `data-slot="command-group-heading"` | a heading  | Targets a heading from a parent   |
| `data-slot="command-item"`          | an item    | Targets items from a parent       |
| `data-highlighted`, `data-disabled` | an item    | Base UI writes them               |
| `data-slot="dialog-content"`        | the dialog | Targets the animated dialog panel |
