# Dropdown menu

> Actions behind a button: the menu rises off the trigger, one highlight glides between rows, and checkbox rows draw their tick in place.

Menus usually repaint each row's background as you move, so the highlight blinks from row to row. Here one highlight glides, typing jumps to a row, checkbox rows draw their tick and keep the menu open, and destructive rows are red. It rises off its trigger and flips above when there is no room.

## Install

```bash
npx shadcn@latest add https://hairlineui.com/r/dropdown-menu.json
```

Needs React 19, Tailwind CSS v4 and shadcn/ui theme variables (any style, light and dark). Icons from lucide-react.

- npm: `lucide-react`
- Hairline UI components it needs (the CLI installs them too): `ui-interaction-motion`
- Docs and live preview: https://hairlineui.com/ui/dropdown-menu
- Registry item (source): https://hairlineui.com/r/dropdown-menu.json

## Usage

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

<DropdownMenu
  trigger={<button aria-label="More">…</button>}
  items={[
    { id: "rename", label: "Rename", shortcut: "F2", onSelect: rename },
    { type: "separator" },
    { id: "delete", label: "Delete", destructive: true, onSelect: remove },
  ]}
/>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `trigger` | `ReactElement` | – | The button that opens it. |
| `items` | `MenuEntry[]` | – | Rows ({ id, label, icon, shortcut, destructive, disabled, onSelect }), { type: "checkbox", checked, onCheckedChange }, { type: "label" } and { type: "separator" }. |
| `side / align` | `Side / Align` | `"bottom" / "start"` | Where it sits. |
| `open / onOpenChange` | `boolean / (open) => void` | – | Control it. |
| `minWidth` | `number` | `200` | Never narrower than the trigger. |
| `label` | `string` | – | Accessible name of the menu. |

## States

- **open**: Rises off the trigger (320ms); flips above if needed.
- **move**: The highlight glides to the row under the pointer or the key.
- **pick**: Closes (180ms) and focus returns to the trigger; checkbox rows stay open and draw their tick (280ms).

## What it handles

Menus need roving focus, typeahead, Home/End and focus return; the gliding highlight is what makes it feel made rather than assembled.

- **Gliding highlight**: One fill moves between rows (220ms) for pointer and keys alike.
- **Keyboard**: Enter/Space/↓ open on the first row, ↑ on the last; arrows wrap; typing jumps.
- **Checkable rows**: A drawn tick, aria-checked, menu stays open.
- **Plain data**: items: rows, labels and separators as objects.

## Replaces

Radix DropdownMenu, floating-ui, roving tabindex

## Where it fits

- **Row actions**: Edit, duplicate, delete on a "…" button.
- **Account menus**: Profile, settings, sign out under an avatar.
- **View options**: Checkable columns or display settings.
- **Create menus**: New doc, new folder, upload.

## Notes

- Placement, presence and dismissal live in floating.ts, shared by tooltip, popover, dropdown and context menus: they all flip, shift, enter and leave the same way.
- role="menu" with menuitem and menuitemcheckbox rows.
- Reduced motion: the menu fades; the highlight jumps.

License: MIT (https://hairlineui.com/license#components)
