# DropdownMenu

> A menu of actions or options opened from a button, with groups, checkable items and submenus.

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

Use DropdownMenu for a set of actions that apply to the current context, such as everything you can do with a signature template. For choosing a value in a form, use Select; for site navigation, use NavigationMenu. DropdownMenu implements the WAI-ARIA menu pattern with roving focus, typeahead and submenus, and has no third-party dependencies.

## Import

```tsx
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent } from '@bindshq/stance-react';
```

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

## Examples

### Basic

```tsx
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Icon } from '@bindshq/stance-react';

export function Basic() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" trailingIcon={<Icon name="chevron-down" />}>
          Template actions
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem>Edit template</DropdownMenuItem>
        <DropdownMenuItem>Duplicate</DropdownMenuItem>
        <DropdownMenuItem>Assign to users</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem destructive>Delete template</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}
```

### Icons and shortcuts

Icons sit before the label; shortcuts are hints only and are hidden from assistive technology.

```tsx
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, Icon } from '@bindshq/stance-react';

export function WithIconsAndShortcuts() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="secondary">Signature</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem icon={<Icon name="pencil" />}>
          Edit <DropdownMenuShortcut>⌘E</DropdownMenuShortcut>
        </DropdownMenuItem>
        <DropdownMenuItem icon={<Icon name="copy" />}>
          Copy HTML <DropdownMenuShortcut>⌘C</DropdownMenuShortcut>
        </DropdownMenuItem>
        <DropdownMenuItem icon={<Icon name="download" />}>Download as image</DropdownMenuItem>
        <DropdownMenuItem icon={<Icon name="send" />} disabled>
          Deploy (no integration)
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem icon={<Icon name="trash" />} destructive>
          Delete
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}
```

### Checkbox and radio items

Checkable items keep the menu open so several settings can be changed at once.

```tsx
import * as React from 'react';
import { Button, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, Icon } from '@bindshq/stance-react';

export function Checkable() {
  const [banner, setBanner] = React.useState(true);
  const [social, setSocial] = React.useState(false);
  const [layout, setLayout] = React.useState('horizontal');
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" leadingIcon={<Icon name="sliders" />}>
          Preview options
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuLabel>Show</DropdownMenuLabel>
        <DropdownMenuCheckboxItem checked={banner} onCheckedChange={setBanner}>
          Promotional banner
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem checked={social} onCheckedChange={setSocial}>
          Social icons
        </DropdownMenuCheckboxItem>
        <DropdownMenuSeparator />
        <DropdownMenuLabel>Layout</DropdownMenuLabel>
        <DropdownMenuRadioGroup value={layout} onValueChange={setLayout}>
          <DropdownMenuRadioItem value="horizontal">Horizontal</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="stacked">Stacked</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="compact">Compact</DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}
```

### Submenus

Open with ArrowRight or hover; close with ArrowLeft or Escape.

```tsx
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Icon } from '@bindshq/stance-react';

export function Submenu() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Share contract</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem icon={<Icon name="link" />}>Copy link</DropdownMenuItem>
        <DropdownMenuSub>
          <DropdownMenuSubTrigger icon={<Icon name="users" />}>
            Share with team
          </DropdownMenuSubTrigger>
          <DropdownMenuSubContent>
            <DropdownMenuItem>Legal</DropdownMenuItem>
            <DropdownMenuItem>Finance</DropdownMenuItem>
            <DropdownMenuItem>Sales</DropdownMenuItem>
          </DropdownMenuSubContent>
        </DropdownMenuSub>
        <DropdownMenuItem icon={<Icon name="mail" />}>Email a copy</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}
```

### Labelled groups

```tsx
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Icon } from '@bindshq/stance-react';

export function Groups() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="ghost" leadingIcon={<Icon name="user" />}>
          Amara Okafor
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuGroup label="Account">
          <DropdownMenuItem icon={<Icon name="user" />}>Profile</DropdownMenuItem>
          <DropdownMenuItem icon={<Icon name="settings" />}>Settings</DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuGroup label="Organisation">
          <DropdownMenuItem icon={<Icon name="building" />}>Binds Ltd</DropdownMenuItem>
          <DropdownMenuItem icon={<Icon name="credit-card" />}>Billing</DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuItem icon={<Icon name="log-out" />}>Sign out</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}
```

## API reference

| Component | Prop | Type | Default | Description |
| --- | --- | --- | --- | --- |
| DropdownMenu | `open` | `boolean` | None | Controlled open state. |
| DropdownMenu | `defaultOpen` | `boolean` | `false` | Initial open state when uncontrolled. |
| DropdownMenu | `onOpenChange` | `(open: boolean) => void` | None | Called when the menu opens or closes. |
| DropdownMenuTrigger | `asChild` | `boolean` | `false` | Render your own button (e.g. a Stance Button) as the trigger. |
| DropdownMenuContent | `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'bottom'` | Preferred side; flips when there is no room. |
| DropdownMenuContent | `align` | `'start' \| 'center' \| 'end'` | `'start'` | Alignment against the trigger. |
| DropdownMenuContent | `sideOffset` | `number` | `6` | Gap between trigger and menu in px. |
| DropdownMenuContent | `loop` | `boolean` | `true` | Wrap arrow-key focus from last to first item. |
| DropdownMenuContent | `onEscapeKeyDown` | `(event: KeyboardEvent) => void` | None | Call preventDefault to keep the menu open. |
| DropdownMenuItem | `onSelect` | `(event: MenuSelectEvent) => void` | None | Called on click, Enter or Space. preventDefault keeps the menu open. |
| DropdownMenuItem | `icon` | `ReactNode` | None | Leading icon. |
| DropdownMenuItem | `destructive` | `boolean` | `false` | Danger styling for irreversible actions. |
| DropdownMenuItem | `disabled` | `boolean` | `false` | Skipped by keyboard and not selectable. |
| DropdownMenuItem | `closeOnSelect` | `boolean` | `true` | Close the whole menu after selection. |
| DropdownMenuItem | `textValue` | `string` | None | Text for typeahead when children are not plain text. |
| DropdownMenuItem | `inset` | `boolean` | `false` | Indent to align with checkable items. |
| DropdownMenuCheckboxItem | `checked` | `boolean \| 'indeterminate'` | `false` | Checked state. |
| DropdownMenuCheckboxItem | `onCheckedChange` | `(checked: boolean) => void` | None | Called when toggled. |
| DropdownMenuCheckboxItem | `closeOnSelect` | `boolean` | `false` | Checkable items stay open by default. |
| DropdownMenuRadioGroup | `value / defaultValue` | `string` | None | The selected radio value. |
| DropdownMenuRadioGroup | `onValueChange` | `(value: string) => void` | None | Called when a radio item is chosen. |
| DropdownMenuRadioItem | `value` (required) | `string` | None | Value of this option. |
| DropdownMenuGroup | `label` | `ReactNode` | None | Visible heading that also labels the group. |
| DropdownMenuLabel | `inset` | `boolean` | None | Non-interactive heading text. |
| DropdownMenuShortcut | `children` | `ReactNode` | None | Keyboard hint shown at the end of an item (aria-hidden). |
| DropdownMenuSubTrigger | `icon / disabled` | `ReactNode / boolean` | None | Item that opens a nested DropdownMenuSubContent. |
| DropdownMenuSubContent | `sideOffset / alignOffset` | `number` | `2 / -5` | Offsets of the submenu against its trigger. |

## Accessibility

- The trigger has aria-haspopup="menu", aria-expanded and aria-controls; the menu is labelled by the trigger.
- Items use role menuitem, menuitemcheckbox or menuitemradio with aria-checked, so state is announced.
- Focus moves into the menu on open and returns to the trigger when it closes by Escape, selection or Tab.
- Disabled items expose aria-disabled and are skipped by arrow keys.
- Clicks inside a submenu never dismiss the parent; clicks outside dismiss the whole menu.
- High contrast mode adds a visible focus ring to the highlighted item; Windows forced colours use the system Highlight.

## Keyboard

| Keys | Action |
| --- | --- |
| `Enter` `Space` `ArrowDown` | On the trigger: open and focus the first item. |
| `ArrowUp` | On the trigger: open and focus the last item. |
| `ArrowDown` `ArrowUp` | Move between items (wraps). |
| `Home` `End` | First or last item. |
| `A to Z` | Typeahead to the next item starting with the typed text. |
| `Enter` `Space` | Activate the focused item. |
| `ArrowRight` | Open a submenu. |
| `ArrowLeft` | Close a submenu and return to its trigger. |
| `Escape` | Close the current menu level. |
| `Tab` | Close the menu. |

## Guidelines

**Do**

- Group related actions and put destructive ones last, after a separator.
- Keep labels short and verb-first: “Duplicate”, “Assign to users”.

**Don’t**

- Use a menu for navigation links; use NavigationMenu.
- Nest submenus more than one level deep.

## Related

- https://stanceui.com/components/action-menu
- https://stanceui.com/components/context-menu
- https://stanceui.com/components/menubar
- https://stanceui.com/components/split-button
- https://stanceui.com/components/select
