# Dropdown Menu (/docs/dropdown-menu)

A Base UI menu with submenus, checkable items, groups, and keyboard navigation.

## Install

```bash
npx shadcn@latest add https://4af.selfsimilar.dev/r/dropdown-menu.json
```

## Usage

```tsx
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

<DropdownMenu>
  <DropdownMenuTrigger>Actions</DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>Edit</DropdownMenuItem>
    <DropdownMenuItem disabled disabledReason="Billing is available on the Pro plan.">
      Billing
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>
```

## API

**Main exports:** root, trigger, content, item, checkbox, radio, submenu, shortcut, and
nested-select parts

A Base UI menu with keyboard navigation, safe submenu pointer travel, keyboard shortcut hints,
destructive rows, navigation submenus, and searchable single and multi-select subpanels.

`DropdownMenu loader={<MySpinner />}` supplies the menu's loading indicator, including portalled
rows. Omit it to inherit [LoaderProvider](/docs/reference/engineering#shared-loading-indicators),
with `AsciiLoader` as the fallback. `DropdownMenuActionItem` and `DropdownMenuCheckboxItem` accept
`loader` for one row; a custom `icons.pending` takes precedence.

A disabled row can take a `disabledReason`. Hovering the row then opens a tooltip on its right
explaining why, without the usual tooltip delay:

```tsx
<DropdownMenuItem disabled disabledReason="Billing is available on the Pro plan.">
  Billing
</DropdownMenuItem>
```

## Nested panels

Dropdown Menu includes three nested-panel forms:

- a navigation submenu;
- a single-select panel with the chosen value shown in the parent row and a checkmark in the
  panel;
- a multi-select panel using small Checkbox components and an `N selected` parent label.

Single- and multi-select panels can include in-panel search. Their parent highlight stays active
while the pointer is on the parent, inside the nested panel, or moving through the safe pointer
corridor.

Nested panels open immediately when their parent row is hovered; the safe pointer corridor still
keeps them open while the pointer moves into the panel. The initially focused child row stays
unhighlighted until it is hovered or reached through keyboard navigation.

Select-style triggers keep their label or placeholder on the left and show the selected value or
selection count, dimmed, immediately before the chevron. Searchable panels use a plain text field
without a leading search icon, and the no-results state is the same height as one option row.

A checkbox item can also read as a toggle button rather than a ticked row. Its `children` may be
a function of checked state, and `indicator={false}` drops the left tick and its indent so the
icon and label carry the state:

```tsx
<DropdownMenuCheckboxItem indicator={false}>
  {(checked) => (
    <>
      <Morph activeKey={checked ? "on" : "off"} items={{ on: <BellOff />, off: <Bell /> }} />
      <Morph activeKey={checked ? "on" : "off"} items={{ on: "Unmute", off: "Mute" }} />
    </>
  )}
</DropdownMenuCheckboxItem>
```

Use `fillIcon` instead when the glyph keeps its shape, such as a bookmark, star, or heart that
only fills in. Either way, the row keeps its `menuitemcheckbox` semantics.

Rows left-align every Morph inside them and mute a morphing leading icon to match a plain one.
Don't pass `place-items-start` or a text colour at the call site.

## Asynchronous rows

`DropdownMenuActionItem` runs an action, shows the result, and reverts, like Action Button:

```tsx
<DropdownMenuActionItem
  icon={<Link2 />}
  action={() => navigator.clipboard.writeText(url)}
  labels={{ success: "Copied" }}
  announce={{ success: "Link copied" }}
>
  Copy link
</DropdownMenuActionItem>
```

It defaults to `closeOnClick={false}` so the result stays visible. Override it when the result
shows on the page behind the menu instead. It takes the same `labels`, `icons`, `revertAfter`, `pendingDelay`, `announce`, `onSuccess`, and
`onError` as Action Button, and both morph between states without resizing the row.

For a row that should stay in its new state, give `DropdownMenuCheckboxItem` an `action`. It
keeps its current state while pending and calls `onCheckedChange` only after success, like
Action Toggle:

```tsx
<DropdownMenuCheckboxItem action={(muted) => setMuted(id, muted)}>
  Mute thread
</DropdownMenuCheckboxItem>
```

Without `action`, it is a plain synchronous checkbox. With it, the tick stays and the loader runs
in its slot until the work settles; on failure the state is unchanged. With `indicator={false}`
there is no tick slot for the loader, so the children function receives
status as a second argument for the caller to render.

Both async rows announce their status from outside the row element, so the status text never
becomes part of the row's accessible name.