# CommandMenu

> A searchable, keyboard-driven list of commands with groups, keywords and shortcuts.

Category: Navigation · Status: stable · Package: @bindshq/stance-react@0.1.1 · Docs: https://stanceui.com/components/command-menu

CommandMenu combines a search field with a filtered list of actions. It is the building block for CommandPalette and can also be embedded inline, for example in an empty state or a sidebar quick-switcher. Filtering uses a built-in fuzzy scorer over each item’s value and keywords; pass your own scorer or disable filtering for server-side search.

## Import

```tsx
import { CommandMenu, CommandInput, CommandList, CommandGroup, CommandItem, CommandSeparator, CommandEmpty, CommandShortcut } from '@bindshq/stance-react';
```

Styles are imported once per app: `@bindshq/stance-tokens/css` and `@bindshq/stance-react/styles.css`.

## Examples

### Basic

```tsx
import * as React from 'react';
import { CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandMenu, CommandSeparator, Icon } from '@bindshq/stance-react';

const box: React.CSSProperties = { width: 'min(440px, 100%)' };

export function Basic() {
  const [last, setLast] = React.useState('');
  return (
    <div style={{ ...box, display: 'grid', gap: 8 }}>
      <CommandMenu label="Quick actions" onItemSelect={setLast}>
        <CommandInput />
        <CommandList>
          <CommandGroup heading="Templates">
            <CommandItem value="New template" icon={<Icon name="plus" />} shortcut="⌘N" />
            <CommandItem value="Browse gallery" icon={<Icon name="layout-grid" />} />
          </CommandGroup>
          <CommandSeparator />
          <CommandGroup heading="Organisation">
            <CommandItem value="Invite user" icon={<Icon name="users" />} shortcut="⌘I" />
            <CommandItem value="Open integrations" icon={<Icon name="link" />} />
            <CommandItem value="Settings" icon={<Icon name="settings" />} shortcut="⌘," />
          </CommandGroup>
        </CommandList>
        <CommandEmpty />
      </CommandMenu>
      <span aria-live="polite">{last && `Selected: ${last}`}</span>
    </div>
  );
}
```

### Keywords and disabled items

Typing “create” finds “New template”.

```tsx
import * as React from 'react';
import { CommandEmpty, CommandInput, CommandItem, CommandList, CommandMenu, Icon } from '@bindshq/stance-react';

const box: React.CSSProperties = { width: 'min(440px, 100%)' };

export function WithKeywords() {
  return (
    <CommandMenu label="Template actions" style={box}>
      <CommandInput placeholder="Search actions…" />
      <CommandList>
        <CommandItem
          value="New template"
          keywords={['create', 'add']}
          icon={<Icon name="plus" />}
        />
        <CommandItem
          value="Import from Outlook"
          keywords={['upload']}
          icon={<Icon name="upload" />}
          disabled
        />
        <CommandItem
          value="Export as HTML"
          keywords={['download']}
          icon={<Icon name="download" />}
        />
      </CommandList>
      <CommandEmpty>No actions match. Try “create” or “download”.</CommandEmpty>
    </CommandMenu>
  );
}
```

### Custom filtering

Disable built-in filtering and render your own results.

```tsx
import * as React from 'react';
import { CommandEmpty, CommandInput, CommandItem, CommandList, CommandMenu, Icon } from '@bindshq/stance-react';

const box: React.CSSProperties = { width: 'min(440px, 100%)' };

const people = [
  'Ada Lovelace',
  'Grace Hopper',
  'Katherine Johnson',
  'Alan Turing',
  'Margaret Hamilton',
];

export function ServerFiltering() {
  const [search, setSearch] = React.useState('');
  // Stand-in for a server query: case-insensitive "contains".
  const results = people.filter((p) => p.toLowerCase().includes(search.toLowerCase()));
  return (
    <CommandMenu
      label="Find a person"
      search={search}
      onSearchChange={setSearch}
      filter={false}
      style={box}
    >
      <CommandInput placeholder="Search people…" />
      <CommandList>
        {results.map((p) => (
          <CommandItem key={p} value={p} icon={<Icon name="user" />} />
        ))}
      </CommandList>
      <CommandEmpty>No people found.</CommandEmpty>
    </CommandMenu>
  );
}
```

## API reference

| Component | Prop | Type | Default | Description |
| --- | --- | --- | --- | --- |
| CommandMenu | `label` | `string` | `'Commands'` | Accessible name for the input and listbox. |
| CommandMenu | `search / defaultSearch / onSearchChange` | `string / string / (search: string) => void` | None | Controlled or uncontrolled search text. |
| CommandMenu | `filter` | `((text: string, search: string) => number) \| false` | `fuzzyScore` | Return 0 to hide an item; false disables filtering. |
| CommandMenu | `onItemSelect` | `(value: string) => void` | None | Called after any item is selected. |
| CommandInput | `placeholder` | `string` | `'Type a command or search…'` | Input placeholder. |
| CommandInput | `hideIcon` | `boolean` | `false` | Hide the search icon. |
| CommandList | `...props` | `HTMLAttributes<HTMLDivElement>` | None | The listbox. |
| CommandGroup | `heading` | `ReactNode` | None | Group heading and accessible name. Empty groups hide automatically. |
| CommandItem | `value` (required) | `string` | None | Unique value used for filtering and passed to onSelect. |
| CommandItem | `keywords` | `string[]` | None | Extra search terms. |
| CommandItem | `onSelect` | `(value: string) => void` | None | Runs on click or Enter. |
| CommandItem | `icon / shortcut` | `ReactNode` | None | Leading icon and trailing shortcut hint. |
| CommandItem | `disabled` | `boolean` | `false` | Visible but not selectable; skipped by arrow keys. |
| CommandItem | `forceMount` | `boolean` | `false` | Always render regardless of the search. |
| CommandEmpty | `children` | `ReactNode` | `'No results found.'` | Shown and announced when nothing matches. Place after CommandList. |

## Accessibility

- Follows the WAI-ARIA combobox pattern: the input has role="combobox" with aria-controls and aria-activedescendant pointing at the highlighted option.
- Focus stays in the input; arrow keys move the highlight, so typing is never interrupted.
- Groups are role="group" named by their heading; separators are hidden from assistive technology.
- The empty message lives in a polite status region.

## Keyboard

| Keys | Action |
| --- | --- |
| `↓` `↑` | Move the highlight, wrapping at the ends and skipping disabled items. |
| `PageUp` `PageDown` | Jump to the first or last item. |
| `Enter` | Run the highlighted command. |

## Guidelines

**Do**

- Name commands as verbs: “Invite user”, “Open integrations”.
- Add keywords for synonyms people actually type.

**Don’t**

- Put more than ~50 items in one group without search hints.

## Related

- https://stanceui.com/components/command-palette
- https://stanceui.com/components/combobox
- https://stanceui.com/components/searchable-list
