# Command

A search field above a list that the user filters and moves through with the keyboard.

```tsx
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
  toast,
  useModKey,
} from 'ferry-ui'
import { FolderKanban, Plus, Settings, UserPlus } from 'lucide-react'

export default function CommandHero() {
  const mod = useModKey()
  return (
    <Command label="Commands" className="max-w-md border border-border-strong">
      <CommandInput placeholder="Type a command or search…" />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup heading="Projects">
          <CommandItem onSelect={() => toast('Open the project Billing portal')}>
            <FolderKanban /> Billing portal
          </CommandItem>
          <CommandItem onSelect={() => toast('Open the project Customer dashboard')}>
            <FolderKanban /> Customer dashboard
          </CommandItem>
        </CommandGroup>
        <CommandSeparator />
        <CommandGroup heading="Actions">
          <CommandItem onSelect={() => toast('New project')}>
            <Plus /> New project
            <CommandShortcut>{mod} N</CommandShortcut>
          </CommandItem>
          <CommandItem onSelect={() => toast('Invite a member')}>
            <UserPlus /> Invite a member
            <CommandShortcut>{mod} I</CommandShortcut>
          </CommandItem>
          <CommandItem onSelect={() => toast('Open the settings')}>
            <Settings /> Open settings
          </CommandItem>
        </CommandGroup>
      </CommandList>
    </Command>
  )
}
```

## Usage guidelines

- **A list with a search.** Use `Command` for a long list that the user filters with the keyboard.
- **A short list needs no search.** For a few actions, use [Dropdown Menu](/docs/components/dropdown-menu). For a form value among 4 to 15 options, use [Select](/docs/components/select).
- **A picker opens from a field.** Put `Command` in a [Popover](/docs/components/popover) to pick one value in a long list.
- **The app has one command menu.** To search the full app, use [Command Menu](/docs/components/command-menu), not `CommandDialog`.

## Anatomy

Import the parts. Put them together in this order.

```tsx title="Anatomy"

  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from 'ferry-ui'

<Command>
  <CommandInput />
  <CommandList>
    <CommandEmpty />
    <CommandGroup>
      <CommandItem>
        <CommandShortcut />
      </CommandItem>
    </CommandGroup>
    <CommandSeparator />
  </CommandList>
</Command>

<CommandDialog>
  <CommandInput />
  <CommandList />
</CommandDialog>
```

| Part | Role |
| --- | --- |
| `Command` | The root. It filters the items and holds the selection. |
| `CommandInput` | The search field. |
| `CommandList` | Holds the groups and the items. It scrolls. |
| `CommandEmpty` | The message that shows when no item matches. |
| `CommandGroup` | A section of items. `heading` sets its title. |
| `CommandItem` | One row that the user can select. |
| `CommandSeparator` | A line between two groups. |
| `CommandShortcut` | A key hint at the right of an item. It binds no key. |
| `CommandDialog` | A `Command` in a dialog. |

## Examples

### Items

`CommandItem` runs `onSelect` on a click and on <Kbd>Enter</Kbd>. The search reads the text of each item. Use `value` to give a different text to the search. Use `keywords` to add more words.

Set `disabled` on an item that the user cannot select.

```tsx
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, toast } from 'ferry-ui'
import { CreditCard, KeyRound, LayoutDashboard, Users } from 'lucide-react'

export default function CommandItems() {
  return (
    <Command label="Pages" className="max-w-sm border border-border-strong">
      <CommandInput placeholder="Go to a page…" />
      <CommandList>
        <CommandEmpty>No page found.</CommandEmpty>
        <CommandGroup heading="Go to">
          <CommandItem onSelect={() => toast('Open the dashboard')}>
            <LayoutDashboard /> Dashboard
          </CommandItem>
          {/* `keywords`: the search for "invoices" also finds this item. */}
          <CommandItem keywords={['invoices', 'payment', 'plan']} onSelect={() => toast('Open the billing page')}>
            <CreditCard /> Billing
          </CommandItem>
          {/* `value`: the search reads this text, not the content with the count. */}
          <CommandItem value="Members" onSelect={() => toast('Open the members page')}>
            <Users /> Members
            <span className="ml-auto text-xs text-foreground-lighter">12</span>
          </CommandItem>
          <CommandItem disabled>
            <KeyRound /> API keys
          </CommandItem>
        </CommandGroup>
      </CommandList>
    </Command>
  )
}
```

### Picker in a popover

Put `Command` in `PopoverContent` to make a picker with a search. Control the open state of the popover. Close the popover in `onSelect`.

```tsx
import * as React from 'react'
import {
  Button,
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  Popover,
  PopoverContent,
  PopoverTrigger,
} from 'ferry-ui'
import { Check, ChevronsUpDown } from 'lucide-react'

const MEMBERS = [
  { id: 'maya', name: 'Maya Chen' },
  { id: 'sam', name: 'Sam Lee' },
  { id: 'ada', name: 'Ada Park' },
  { id: 'liam', name: 'Liam Chen' },
  { id: 'sofia', name: 'Sofia Rossi' },
]

export default function CommandCombobox() {
  const [open, setOpen] = React.useState(false)
  const [owner, setOwner] = React.useState<string>()
  const current = MEMBERS.find((member) => member.id === owner)

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <Button role="combobox" aria-expanded={open} iconRight={<ChevronsUpDown />} className="w-56 justify-between">
          {current?.name ?? 'Select an owner'}
        </Button>
      </PopoverTrigger>
      <PopoverContent align="start" className="w-56 p-0" aria-label="Owner">
        <Command>
          <CommandInput placeholder="Find a member…" />
          <CommandList>
            <CommandEmpty>No member found.</CommandEmpty>
            <CommandGroup heading="Members">
              {MEMBERS.map((member) => (
                <CommandItem
                  key={member.id}
                  value={member.name}
                  onSelect={() => {
                    setOwner(member.id)
                    setOpen(false)
                  }}
                >
                  {member.name}
                  {member.id === owner && <Check className="ml-auto" />}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  )
}
```

### Your own filter

If your code filters the list, pass `shouldFilter={false}`. This is necessary for a search on a server. Control `CommandInput` with `value` and `onValueChange`. Render only the items that match.

```tsx
import * as React from 'react'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, toast } from 'ferry-ui'
import { FileText } from 'lucide-react'

const INVOICES = [
  { id: 'INV-2041', customer: 'Acme', amount: '$4,200.00' },
  { id: 'INV-2042', customer: 'Maya Chen', amount: '$860.00' },
  { id: 'INV-2043', customer: 'Sam Lee', amount: '$12,940.50' },
  { id: 'INV-2044', customer: 'Ada Park', amount: '$1,315.00' },
]

export default function CommandOwnFilter() {
  const [query, setQuery] = React.useState('')
  const text = query.trim().toLowerCase()
  // Your code filters the list. With a search on the server, the results come from the request.
  const results = INVOICES.filter((invoice) => `${invoice.id} ${invoice.customer}`.toLowerCase().includes(text))

  return (
    <Command shouldFilter={false} label="Invoices" className="max-w-md border border-border-strong">
      <CommandInput value={query} onValueChange={setQuery} placeholder="Search by number or customer…" />
      <CommandList>
        <CommandEmpty>No invoice matches “{query}”.</CommandEmpty>
        {results.length > 0 && (
          <CommandGroup heading={`Invoices · ${results.length}`}>
            {results.map((invoice) => (
              <CommandItem key={invoice.id} value={invoice.id} onSelect={() => toast(`Open the invoice ${invoice.id}`)}>
                <FileText />
                <span className="font-mono text-xs text-foreground-lighter">{invoice.id}</span>
                <span className="truncate">{invoice.customer}</span>
                <span className="tabular ml-auto text-xs text-foreground-lighter">{invoice.amount}</span>
              </CommandItem>
            ))}
          </CommandGroup>
        )}
      </CommandList>
    </Command>
  )
}
```

### Command in a dialog

`CommandDialog` shows a `Command` in a [Dialog](/docs/components/dialog). Pass `open` and `onOpenChange`. The `commandProps` prop takes the props of the `Command` root.

`CommandDialog` binds no key. To open it with a shortcut, use [useCommandShortcut](/docs/utilities/use-command-shortcut).

```tsx
import * as React from 'react'
import { Button, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, toast } from 'ferry-ui'
import { Users } from 'lucide-react'

const MEMBERS = [
  { name: 'Maya Chen', email: 'maya@example.com' },
  { name: 'Sam Lee', email: 'sam@example.com' },
  { name: 'Ada Park', email: 'ada@example.com' },
  { name: 'Liam Chen', email: 'liam@example.com' },
]

export default function CommandInDialog() {
  const [open, setOpen] = React.useState(false)

  return (
    <>
      <Button icon={<Users />} onClick={() => setOpen(true)}>
        Find a member
      </Button>
      <CommandDialog
        open={open}
        onOpenChange={setOpen}
        title="Find a member"
        description="Search the members of the workspace by name or by email."
      >
        <CommandInput placeholder="Search by name or email…" />
        <CommandList>
          <CommandEmpty>No member found.</CommandEmpty>
          <CommandGroup heading="Members">
            {MEMBERS.map((member) => (
              <CommandItem
                key={member.email}
                value={`${member.name} ${member.email}`}
                onSelect={() => {
                  toast(`Open the profile of ${member.name}`)
                  setOpen(false)
                }}
              >
                {member.name}
                <span className="ml-auto truncate text-xs text-foreground-lighter">{member.email}</span>
              </CommandItem>
            ))}
          </CommandGroup>
        </CommandList>
      </CommandDialog>
    </>
  )
}
```

## Accessibility

- <Kbd>↑</Kbd> and <Kbd>↓</Kbd> move the selection. <Kbd>Enter</Kbd> runs the selected item.
- The `label` prop of `Command` gives an accessible name to the search field. It does not show.
- `CommandDialog` has a hidden title and a hidden description. Set them with `title` and `description`.
- For a picker, give `role="combobox"` and `aria-expanded` to the trigger. Give an `aria-label` to `PopoverContent`.

## API reference

Each part also accepts the props of its cmdk primitive and the attributes of its element.

### Command

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |
| `defaultValue` | `string \| (readonly string[] & string)` |  | Optional default item value when it is initially rendered. |
| `asChild` | `boolean` |  |  |
| `label` | `string` |  | Accessible label for this command menu. Not shown visibly. |
| `shouldFilter` | `boolean` |  | Optionally set to `false` to turn off the automatic filtering and sorting. If `false`, you must conditionally render valid items based on the search query yourself. |
| `filter` | `CommandFilter` |  | Custom filter function for whether each command menu item should matches the given search query. It should return a number between 0 and 1, with 1 being the best match and 0 being hidden entirely. By default, uses the `command-score` library. |
| `value` | `string` |  | Optional controlled state of the selected command menu item. |
| `onValueChange` | `((value: string) => void)` |  | Event handler called when the selected item of the menu changes. |
| `loop` | `boolean` |  | Optionally set to `true` to turn on looping around when using the arrow keys. |
| `disablePointerSelection` | `boolean` |  | Optionally set to `true` to disable selection via pointer events. |
| `vimBindings` | `boolean` |  | Set to `false` to disable ctrl+n/j/p/k shortcuts. Defaults to `true`. |

### CommandInput

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `value` | `string` |  | Optional controlled state for the value of the search input. |
| `onValueChange` | `((search: string) => void)` |  | Event handler called when the search value changes. |

### CommandList

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |
| `asChild` | `boolean` |  |  |
| `label` | `string` |  | Accessible label for this List of suggestions. Not shown visibly. |

### CommandEmpty

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |
| `asChild` | `boolean` |  |  |

### CommandGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |
| `asChild` | `boolean` |  |  |
| `heading` | `ReactNode` |  | Optional heading to render for this group. |
| `value` | `string` |  | If no heading is provided, you must provide a value that is unique for this group. |
| `forceMount` | `boolean` |  | Whether this group is forcibly rendered regardless of filtering. |

### CommandItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |
| `asChild` | `boolean` |  |  |
| `disabled` | `boolean` |  | Whether this item is currently disabled. |
| `onSelect` | `((value: string) => void)` |  | Event handler for when this item is selected, either via click or keyboard selection. |
| `value` | `string` |  | A unique value for this item. If no value is provided, it will be inferred from `children` or the rendered `textContent`. If your `textContent` changes between renders, you _must_ provide a stable, unique `value`. |
| `keywords` | `string[]` |  | Optional keywords to match against when filtering. |
| `forceMount` | `boolean` |  | Whether this item is forcibly rendered regardless of filtering. |

### CommandSeparator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `alwaysRender` | `boolean` |  | Whether this separator should always be rendered. Useful if you disable automatic filtering. |

### CommandShortcut

`CommandShortcut` has no props of its own. It accepts the attributes of the element it renders.

### CommandDialog

`CommandDialog` also accepts the props of the `Dialog` root.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `string` | `Command Palette` | Accessible dialog title, visually hidden (default "Command Palette"). |
| `description` | `string` | `Search for a command to run...` | Accessible dialog description, visually hidden (default "Search for a command to run..."). |
| `className` | `string` |  | Classes merged onto the dialog panel (e.g. `sm:max-w-2xl` to widen it). |
| `showCloseButton` | `boolean` | `true` | Show the top-right close button (default true). |
| `closeLabel` | `string` |  | Accessible name of the close button (default "Close"); pass a translation in localized apps. |
| `commandProps` | `Omit<ComponentProps<typeof CommandPrimitive>, "children">` |  | Props for the inner Command root, e.g. `shouldFilter={false}` for server-side search, `filter`, a controlled `value` / `onValueChange` for the highlighted item, or `loop={false}`. |
| `children` | `ReactNode` |  |  |
| `open` | `boolean` |  |  |
| `defaultOpen` | `boolean` |  |  |
| `onOpenChange` | `((open: boolean) => void)` |  |  |
| `modal` | `boolean` |  |  |
