# Command

> Search and run actions from an inline list or command palette.

Source: https://www.honestui.com/docs/components/command

---
title: Command
description: Search and run actions from an inline list or command palette.

links:
  doc: https://base-ui.com/react/components/autocomplete#api-reference
---

```tsx
"use client"

import * as React from "react"
import {
  File as FileIcon,
  Palette as PaletteIcon,
  Search as SearchIcon,
  Settings as SettingsIcon,
  User as UserIcon,
} from "honestui/icons"

import {
  Command,
  CommandContent,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandLabel,
  CommandSeparator,
} from "@/components/honest-ui/ui/command"

export function CommandDemo() {
  const [lastAction, setLastAction] = React.useState("No command selected")

  return (
    <div className="w-full max-w-lg space-y-3">
      <Command className="rounded-lg border border-border shadow-sm">
        <CommandInput
          leadingIcon={<SearchIcon />}
          placeholder="Search commands..."
        />
        <CommandContent>
          <CommandEmpty>No matching commands.</CommandEmpty>
          <CommandGroup>
            <CommandLabel>Navigation</CommandLabel>
            <CommandItem
              leadingIcon={<FileIcon />}
              trailingIcon={<span className="text-xs">G then D</span>}
              onClick={() => setLastAction("Opened documents")}
            >
              Open documents
            </CommandItem>
            <CommandItem
              leadingIcon={<UserIcon />}
              onClick={() => setLastAction("Opened account")}
            >
              Open account
            </CommandItem>
          </CommandGroup>
          <CommandSeparator />
          <CommandGroup>
            <CommandLabel>Preferences</CommandLabel>
            <CommandItem
              leadingIcon={<PaletteIcon />}
              onClick={() => setLastAction("Opened appearance")}
            >
              Change appearance
            </CommandItem>
            <CommandItem
              leadingIcon={<SettingsIcon />}
              onClick={() => setLastAction("Opened settings")}
            >
              Open settings
            </CommandItem>
          </CommandGroup>
        </CommandContent>
      </Command>
      <p className="text-center text-xs text-muted-foreground" aria-live="polite">
        {lastAction}
      </p>
    </div>
  )
}

```

## Overview

Use Command to help people find and run actions from a compact, keyboard-oriented list. It works inline for a persistent command browser or inside `CommandDialog` for a temporary command palette.

Command is built on Base UI Autocomplete, so the input and results share highlight state and keyboard navigation. Use it for actions such as opening a document, navigating to a workspace area, or changing a setting. Use Combobox instead when the person is selecting a value for a field rather than running an action.

## Anatomy

Command includes the root panel, search input, scrollable content, items, an empty state, optional groups and labels, and separators. The dialog variant adds a root, trigger, viewport, and popup. All parts and Tailwind styles live in one `command.tsx` file and use named PascalCase exports.

## Behavior

Typing filters JSX items by their `value` or visible text. When you supply the root `items` prop, Base UI owns filtering and supports object values through `itemToStringValue`. The first result is highlighted by default, arrow keys move the highlight, Enter runs the highlighted item, and Escape closes the dialog variant.

Groups and separators disappear during the built-in JSX filtering fallback so results form one continuous list. The empty state appears only when no option remains. Keep item labels action-oriented and use the optional trailing area for concise keyboard hints rather than secondary descriptions.

## Accessibility

`CommandInput` has the default accessible name “Search commands.” Pass a more specific `aria-label` when the command scope is narrower. The dialog content includes a visually hidden title; change its `title` prop so screen-reader users hear the same scope as sighted users.

Base UI manages the combobox, listbox, option, dialog, focus, and keyboard relationships. If you add a global shortcut, support both Control and Command where appropriate, prevent the browser default only for that shortcut, remove the listener when the component unmounts, and keep a visible trigger for people who do not know the shortcut.

## Installation






```bash
npx honestui@latest add command
```







Install the required dependency:

```bash
npm install @base-ui/react
```

Copy and paste the component into your project.

### components/ui/command.tsx

```tsx
"use client"

import * as React from "react"
import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete"
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"

import { cn } from "@/lib/utils"

type CommandContextValue = {
  inputValue: string
  usesItemsProp: boolean
}

const CommandContext = React.createContext<CommandContextValue | null>(null)

function useCommandContext() {
  const context = React.useContext(CommandContext)

  if (!context) {
    throw new Error("Command parts must be used within Command")
  }

  return context
}

function getTextContent(node: React.ReactNode): string {
  return React.Children.toArray(node)
    .map((child) => {
      if (typeof child === "string" || typeof child === "number") {
        return String(child)
      }

      if (React.isValidElement<{ children?: React.ReactNode }>(child)) {
        return getTextContent(child.props.children)
      }

      return ""
    })
    .join(" ")
}

function matchesSearch(
  value: unknown,
  children: React.ReactNode,
  query: string
) {
  const searchableValue =
    typeof value === "string" ? value : getTextContent(children)

  return searchableValue
    .toLocaleLowerCase()
    .includes(query.trim().toLocaleLowerCase())
}

type CommandProps<ItemValue = string> = Omit<
  AutocompletePrimitive.Root.Props<ItemValue>,
  "items"
> & {
  className?: string
  items?: readonly ItemValue[]
}

function Command<ItemValue = string>({
  value: controlledValue,
  defaultValue,
  onValueChange,
  items,
  inline = true,
  open = true,
  autoHighlight = "always",
  keepHighlight = true,
  className,
  children,
  ...props
}: CommandProps<ItemValue>) {
  const [uncontrolledValue, setUncontrolledValue] = React.useState(
    typeof defaultValue === "string" ? defaultValue : ""
  )
  const inputValue =
    typeof controlledValue === "string" ? controlledValue : uncontrolledValue

  const handleValueChange = React.useCallback<
    NonNullable<AutocompletePrimitive.Root.Props<ItemValue>["onValueChange"]>
  >(
    (nextValue, eventDetails) => {
      if (controlledValue === undefined) {
        setUncontrolledValue(nextValue)
      }
      onValueChange?.(nextValue, eventDetails)
    },
    [controlledValue, onValueChange]
  )

  const context = React.useMemo<CommandContextValue>(
    () => ({
      inputValue,
      usesItemsProp: items !== undefined,
    }),
    [inputValue, items]
  )

  return (
    <CommandContext.Provider value={context}>
      <AutocompletePrimitive.Root<ItemValue>
        inline={inline}
        open={open}
        autoHighlight={autoHighlight}
        keepHighlight={keepHighlight}
        value={controlledValue}
        defaultValue={defaultValue}
        onValueChange={handleValueChange}
        items={items}
        {...props}
      >
        <div
          data-slot="command"
          className={cn(
            "isolate flex w-full flex-col overflow-hidden bg-[var(--hui-color-background-base-primary)]",
            className
          )}
        >
          {children}
        </div>
      </AutocompletePrimitive.Root>
    </CommandContext.Provider>
  )
}

type CommandInputProps = Omit<AutocompletePrimitive.Input.Props, "size"> & {
  leadingIcon?: React.ReactNode
  size?: "sm" | "default" | "lg"
}

function CommandInput({
  className,
  leadingIcon,
  size = "lg",
  placeholder = "Search...",
  autoFocus = true,
  "aria-label": ariaLabel = "Search commands",
  ...props
}: CommandInputProps) {
  return (
    <div
      data-slot="command-input-container"
      className="flex flex-col bg-[var(--hui-color-background-base-primary)] p-[var(--hui-space-2)]"
    >
      <div className="flex items-center gap-[var(--hui-space-2)] rounded-[var(--hui-radius-2)] border-0 bg-transparent px-[var(--hui-space-3)] shadow-none outline-none focus-within:bg-[var(--hui-color-background-base-primary-hover)] forced-colors:focus-within:outline forced-colors:focus-within:outline-1 forced-colors:focus-within:outline-offset-[-2px]">
        {leadingIcon ? (
          <span
            aria-hidden="true"
            className="flex shrink-0 items-center justify-center text-[var(--hui-color-foreground-base-secondary)] [&_svg]:size-[var(--hui-space-5)]"
          >
            {leadingIcon}
          </span>
        ) : null}
        <AutocompletePrimitive.Input
          data-slot="command-input"
          type="search"
          aria-label={ariaLabel}
          autoFocus={autoFocus}
          placeholder={placeholder}
          className={cn(
            "m-0 w-full min-w-0 border-0 bg-transparent p-0 text-[var(--hui-color-foreground-base-primary)] shadow-none outline-none [font-size:var(--hui-font-size-small)] [font-weight:var(--hui-font-weight-regular)] [letter-spacing:var(--hui-letter-spacing-small)] [line-height:var(--hui-line-height-small)] placeholder:text-[var(--hui-color-foreground-base-tertiary)] focus-visible:ring-0 focus-visible:outline-none [&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none",
            size === "sm" && "h-[var(--hui-space-7)]",
            size === "default" && "h-[var(--hui-space-9)]",
            size === "lg" && "h-[var(--hui-space-11)]",
            className
          )}
          {...props}
        />
      </div>
    </div>
  )
}

type CommandContentProps = AutocompletePrimitive.List.Props

function CommandContent({ className, ...props }: CommandContentProps) {
  return (
    <AutocompletePrimitive.List
      data-slot="command-content"
      className={cn(
        "flex max-h-80 flex-col overflow-x-hidden overflow-y-auto overscroll-contain px-[var(--hui-space-2)] empty:p-0 [&:has([role=option])>[data-slot=command-empty]]:hidden [&:not(:has([data-slot=command-group]))]:gap-[var(--hui-space-1)] [&:not(:has([data-slot=command-group]))]:py-[var(--hui-space-3)]",
        className
      )}
      {...props}
    />
  )
}

type CommandItemProps = AutocompletePrimitive.Item.Props & {
  leadingIcon?: React.ReactNode
  trailingIcon?: React.ReactNode
}

function CommandItem({
  className,
  children,
  value: providedValue,
  leadingIcon,
  trailingIcon,
  ...props
}: CommandItemProps) {
  const { inputValue, usesItemsProp } = useCommandContext()
  const value =
    providedValue !== undefined
      ? providedValue
      : typeof children === "string"
        ? children
        : undefined

  if (
    !usesItemsProp &&
    inputValue &&
    !matchesSearch(value, children, inputValue)
  ) {
    return null
  }

  return (
    <AutocompletePrimitive.Item
      data-slot="command-item"
      value={value}
      className={cn(
        "flex min-h-9 cursor-pointer scroll-my-[var(--hui-space-2)] items-center gap-[var(--hui-space-3)] rounded-[var(--hui-radius-2)] bg-[var(--hui-color-background-base-primary)] p-[var(--hui-space-3)] text-[var(--hui-color-foreground-base-primary)] outline-none select-none [font-size:var(--hui-font-size-small)] [font-weight:var(--hui-font-weight-regular)] [letter-spacing:var(--hui-letter-spacing-small)] [line-height:var(--hui-line-height-small)] data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-[var(--hui-color-background-base-primary-hover)]",
        className
      )}
      {...props}
    >
      {leadingIcon ? (
        <span
          data-slot="command-item-leading-icon"
          aria-hidden="true"
          className="inline-flex shrink-0 items-center justify-center text-[var(--hui-color-foreground-base-secondary)] [&_svg]:size-[var(--hui-space-5)]"
        >
          {leadingIcon}
        </span>
      ) : null}
      <span
        data-slot="command-item-label"
        className="min-w-0 flex-1 truncate"
      >
        {children}
      </span>
      {trailingIcon ? (
        <span
          data-slot="command-item-trailing-icon"
          className="ms-auto inline-flex shrink-0 items-center justify-center text-[var(--hui-color-foreground-base-tertiary)] [&_svg]:size-[var(--hui-space-5)]"
        >
          {trailingIcon}
        </span>
      ) : null}
    </AutocompletePrimitive.Item>
  )
}

type CommandEmptyProps = React.ComponentProps<"div">

function CommandEmpty({ className, ...props }: CommandEmptyProps) {
  return (
    <div
      data-slot="command-empty"
      className={cn(
        "px-[var(--hui-space-3)] py-[var(--hui-space-7)] text-center text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-small)] [line-height:var(--hui-line-height-small)]",
        className
      )}
      {...props}
    />
  )
}

type CommandGroupProps = AutocompletePrimitive.Group.Props

function CommandGroup({ children, className, ...props }: CommandGroupProps) {
  const { inputValue, usesItemsProp } = useCommandContext()

  if (!usesItemsProp && inputValue) return <>{children}</>

  return (
    <AutocompletePrimitive.Group
      data-slot="command-group"
      className={cn(
        "flex flex-col gap-[var(--hui-space-1)] py-[var(--hui-space-3)]",
        className
      )}
      {...props}
    >
      {children}
    </AutocompletePrimitive.Group>
  )
}

type CommandLabelProps = AutocompletePrimitive.GroupLabel.Props

function CommandLabel({ className, ...props }: CommandLabelProps) {
  const { inputValue, usesItemsProp } = useCommandContext()

  if (!usesItemsProp && inputValue) return null

  return (
    <AutocompletePrimitive.GroupLabel
      data-slot="command-label"
      className={cn(
        "flex items-center px-[var(--hui-space-3)] py-[var(--hui-space-2)] text-[var(--hui-color-foreground-base-tertiary)] [font-size:var(--hui-font-size-micro)] [font-weight:var(--hui-font-weight-regular)] [letter-spacing:var(--hui-letter-spacing-micro)] [line-height:var(--hui-line-height-micro)]",
        className
      )}
      {...props}
    />
  )
}

type CommandSeparatorProps = AutocompletePrimitive.Separator.Props

function CommandSeparator({ className, ...props }: CommandSeparatorProps) {
  const { inputValue, usesItemsProp } = useCommandContext()

  if (!usesItemsProp && inputValue) return null

  return (
    <AutocompletePrimitive.Separator
      data-slot="command-separator"
      className={cn(
        "my-[var(--hui-space-1)] h-px bg-[var(--hui-color-border-base-primary)]",
        className
      )}
      {...props}
    />
  )
}

function CommandDialog(props: DialogPrimitive.Root.Props) {
  return <DialogPrimitive.Root {...props} />
}

function CommandDialogTrigger(props: DialogPrimitive.Trigger.Props) {
  return (
    <DialogPrimitive.Trigger data-slot="command-dialog-trigger" {...props} />
  )
}

type CommandDialogContentProps = DialogPrimitive.Popup.Props & {
  title?: string
  width?: React.CSSProperties["width"]
}

function CommandDialogContent({
  className,
  children,
  title = "Command palette",
  width,
  style,
  ...props
}: CommandDialogContentProps) {
  return (
    <DialogPrimitive.Portal>
      <DialogPrimitive.Viewport
        data-slot="command-dialog-viewport"
        className="pointer-events-none fixed inset-0 z-[var(--hui-z-index-portal)]"
      >
        <DialogPrimitive.Popup
          data-slot="command-dialog-content"
          className={cn(
            "pointer-events-auto fixed top-0 left-1/2 isolate flex max-h-[min(420px,80vh)] min-h-0 w-[540px] max-w-[90vw] flex-col overflow-hidden rounded-[var(--hui-radius-2)] bg-[var(--hui-color-background-base-primary)] shadow-[var(--hui-shadow-floating)] [transform:translate(-50%,min(160px,calc(50vh-50%)))] outline-none motion-safe:[transition:opacity_var(--hui-duration-fast)_var(--hui-ease-out)] motion-safe:data-ending-style:opacity-0 motion-safe:data-starting-style:opacity-0",
            className
          )}
          style={{ width, ...style }}
          {...props}
        >
          <DialogPrimitive.Title className="sr-only">
            {title}
          </DialogPrimitive.Title>
          {children}
        </DialogPrimitive.Popup>
      </DialogPrimitive.Viewport>
    </DialogPrimitive.Portal>
  )
}

export {
  Command,
  CommandContent,
  CommandDialog,
  CommandDialogContent,
  CommandDialogTrigger,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandLabel,
  CommandSeparator,
}

export type {
  CommandContentProps,
  CommandDialogContentProps,
  CommandEmptyProps,
  CommandGroupProps,
  CommandInputProps,
  CommandItemProps,
  CommandLabelProps,
  CommandProps,
  CommandSeparatorProps,
}

```

Update the import paths to match your project setup.







## Usage

```tsx
import {
  Command,
  CommandContent,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandLabel,
} from "@/components/ui/command";
```

```tsx
<Command>
  <CommandInput placeholder="Search commands..." />
  <CommandContent>
    <CommandEmpty>No matching commands.</CommandEmpty>
    <CommandGroup>
      <CommandLabel>Workspace</CommandLabel>
      <CommandItem value="new document">New document</CommandItem>
      <CommandItem value="workspace settings">Workspace settings</CommandItem>
    </CommandGroup>
  </CommandContent>
</Command>
```

## Examples

The inline example demonstrates grouping, filtering, keyboard hints, icons, and visible action feedback. The dialog example adds a persistent trigger and the familiar Control/Command+K shortcut.

### Inline command list

```tsx
"use client"

import * as React from "react"
import {
  File as FileIcon,
  Palette as PaletteIcon,
  Search as SearchIcon,
  Settings as SettingsIcon,
  User as UserIcon,
} from "honestui/icons"

import {
  Command,
  CommandContent,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandLabel,
  CommandSeparator,
} from "@/components/honest-ui/ui/command"

export function CommandDemo() {
  const [lastAction, setLastAction] = React.useState("No command selected")

  return (
    <div className="w-full max-w-lg space-y-3">
      <Command className="rounded-lg border border-border shadow-sm">
        <CommandInput
          leadingIcon={<SearchIcon />}
          placeholder="Search commands..."
        />
        <CommandContent>
          <CommandEmpty>No matching commands.</CommandEmpty>
          <CommandGroup>
            <CommandLabel>Navigation</CommandLabel>
            <CommandItem
              leadingIcon={<FileIcon />}
              trailingIcon={<span className="text-xs">G then D</span>}
              onClick={() => setLastAction("Opened documents")}
            >
              Open documents
            </CommandItem>
            <CommandItem
              leadingIcon={<UserIcon />}
              onClick={() => setLastAction("Opened account")}
            >
              Open account
            </CommandItem>
          </CommandGroup>
          <CommandSeparator />
          <CommandGroup>
            <CommandLabel>Preferences</CommandLabel>
            <CommandItem
              leadingIcon={<PaletteIcon />}
              onClick={() => setLastAction("Opened appearance")}
            >
              Change appearance
            </CommandItem>
            <CommandItem
              leadingIcon={<SettingsIcon />}
              onClick={() => setLastAction("Opened settings")}
            >
              Open settings
            </CommandItem>
          </CommandGroup>
        </CommandContent>
      </Command>
      <p className="text-center text-xs text-muted-foreground" aria-live="polite">
        {lastAction}
      </p>
    </div>
  )
}

```

### Command dialog

```tsx
"use client"

import * as React from "react"
import {
  Command as CommandIcon,
  File as FileIcon,
  Search as SearchIcon,
  Settings as SettingsIcon,
  User as UserIcon,
} from "honestui/icons"

import { Button } from "@/components/honest-ui/ui/button"
import {
  Command,
  CommandContent,
  CommandDialog,
  CommandDialogContent,
  CommandDialogTrigger,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandLabel,
} from "@/components/honest-ui/ui/command"

export function CommandDialogDemo() {
  const [open, setOpen] = React.useState(false)
  const [lastAction, setLastAction] = React.useState("No command selected")

  React.useEffect(() => {
    function handleKeyDown(event: KeyboardEvent) {
      if (event.key.toLocaleLowerCase() === "k" && (event.metaKey || event.ctrlKey)) {
        event.preventDefault()
        setOpen((currentOpen) => !currentOpen)
      }
    }

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

  function runCommand(message: string) {
    setLastAction(message)
    setOpen(false)
  }

  return (
    <div className="flex flex-col items-center gap-3">
      <CommandDialog open={open} onOpenChange={setOpen}>
        <CommandDialogTrigger render={<Button variant="secondary" />}>
          <CommandIcon />
          Open command palette
          <span className="text-xs opacity-72">⌘K</span>
        </CommandDialogTrigger>
        <CommandDialogContent title="Workspace commands">
          <Command>
            <CommandInput
              leadingIcon={<SearchIcon />}
              placeholder="Search workspace commands..."
            />
            <CommandContent>
              <CommandEmpty>No matching commands.</CommandEmpty>
              <CommandGroup>
                <CommandLabel>Workspace</CommandLabel>
                <CommandItem
                  leadingIcon={<FileIcon />}
                  onClick={() => runCommand("Opened a new document")}
                >
                  New document
                </CommandItem>
                <CommandItem
                  leadingIcon={<UserIcon />}
                  onClick={() => runCommand("Opened team members")}
                >
                  View team members
                </CommandItem>
                <CommandItem
                  leadingIcon={<SettingsIcon />}
                  onClick={() => runCommand("Opened workspace settings")}
                >
                  Workspace settings
                </CommandItem>
              </CommandGroup>
            </CommandContent>
          </Command>
        </CommandDialogContent>
      </CommandDialog>
      <p className="text-xs text-muted-foreground" aria-live="polite">
        {lastAction}
      </p>
    </div>
  )
}

```

## API reference

`Command` forwards Base UI Autocomplete root props and adds a panel `className`. `CommandInput` accepts Base UI input props plus `leadingIcon` and `size`. `CommandItem` accepts Base UI item props plus `leadingIcon` and `trailingIcon`.

`CommandDialog` and `CommandDialogTrigger` forward their matching Base UI Dialog props. `CommandDialogContent` accepts popup props plus `title` and `width`. See the [Base UI Autocomplete API](https://base-ui.com/react/components/autocomplete#api-reference) and [Base UI Dialog API](https://base-ui.com/react/components/dialog#api-reference) for controlled state, item collections, filtering, and focus behavior.
