---
title: Dropdown Menu
description: Displays a menu of actions or functions when the user presses a trigger button, with groups, submenus, checkbox and radio items, and shortcuts.
icon: chevron-down
author:
  name: imskyleen
  url: https://github.com/imskyleen
search:
  keywords: [menu, actions menu, kebab menu, filterable menu, searchable menu]
related:
  - /context-menu
  - /menubar
  - /select
mode: wide
---

<PoweredBy
  packages={[
    { name: 'Base UI', url: 'https://base-ui.com/react/components/menu' },
    { name: 'Shadcn UI', url: 'https://ui.shadcn.com/docs/components/dropdown-menu' },
    { name: 'Lucide', url: 'https://lucide.dev/' },
  ]}
/>

### Installation

```package-install
npx shadcn@latest add @redpanda/dropdown-menu
```

<ComponentPreview name="dropdown-menu-demo" />

## When to use

Use this decision tree to determine when to use the Dropdown Menu component:

```mermaid
graph TD;
    A[❓ Need menu with actions?] --> B{What trigger method?};
    B --> C[✅ Click to open menu];
    B --> D[⚠️ Right-click context];
    B --> E[⚠️ Hover activation];

    C --> F[❓ What content type?];
    D --> G[⚠️ Use ContextMenu instead];
    E --> H[⚠️ Use HoverCard instead];

    F --> I[✅ Action items/buttons];
    F --> J[✅ Navigation links];
    F --> K[✅ Settings/preferences];
    F --> L[⚠️ Data selection];

    I --> M[❓ Need grouping?];
    J --> M;
    K --> N[✅ Use checkboxes/radio groups];
    L --> O[⚠️ Use Select/Combobox instead];

    M --> P[✅ Use DropdownMenuGroup + Separator];
    M --> Q[ℹ️ Simple menu items];
    N --> R;

    P --> R[❓ Nested menus needed?];
    Q --> R;

    R --> S[✅ Use DropdownMenuSub];
    R --> T[ℹ️ Flat menu structure];

    S --> U[❓ Animation preferences?];
    T --> U;

    U --> V[✅ Custom hover animations];
    U --> W[ℹ️ Default animations];

    classDef recommended fill:#4ade80,stroke:#22c55e,stroke-width:2px,color:#000;
    classDef discouraged fill:#fbbf24,stroke:#f59e0b,stroke-width:2px,color:#000;
    classDef decision fill:#60a5fa,stroke:#3b82f6,stroke-width:2px,color:#fff;
    classDef neutral fill:#9ca3af,stroke:#6b7280,stroke-width:2px,color:#fff;

    class C,I,J,N,P,S,V recommended;
    class D,E,G,H,L,O discouraged;
    class A,B,F,M,R,U decision;
    class K,Q,T,W neutral;
```

## Usage

```tsx
<DropdownMenu>
  <DropdownMenuTrigger>Open Dropdown Menu</DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>Item 1</DropdownMenuItem>
    <DropdownMenuItem>Item 2</DropdownMenuItem>
    <DropdownMenuItem>Item 3</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>
```

## Examples

### Item types

#### Checkboxes

<ComponentPreview name="dropdown-menu-checkboxes-demo" />

#### Radio group

<ComponentPreview name="dropdown-menu-radio-group-demo" />

#### Link items

<ComponentPreview name="dropdown-menu-links-demo" description="Native menu links carrying current-page state." />

Use `DropdownMenuLinkItem href="..."` for navigation rather than nesting an anchor inside an action item.
The actual menu item is the native link, preserving its URL, browser modifiers, ref, and keyboard navigation.
Set `aria-current="page"` on the current link; the shared menu-row recipe owns its persistent appearance.
Link items preserve Base UI's `closeOnClick={false}` default. Use `closeOnClick` when navigation should close
the current menu, such as the in-page project-summary example below. Links do not have a disabled action API.

#### Keyboard shortcuts

<ComponentPreview
  name="dropdown-menu-with-shortcuts-demo"
  description="Dropdown menu items with trailing keyboard-shortcut indicators."
/>

### Composition

#### Submenus

<ComponentPreview
  name="dropdown-menu-submenu-demo"
  description="Dropdown menu with a nested submenu via SubmenuRoot/SubmenuTrigger."
/>

#### Labeled sections

<ComponentPreview
  name="dropdown-menu-sections-demo"
  description="Dropdown menu with grouped sections separated by labels and separators."
/>

### Filtering

Long menus can take a search field. Wrap the `DropdownMenu` in a `DropdownMenuFilterProvider`, add a
`DropdownMenuInput` to the content, and put the items in a `DropdownMenuList`. Typing narrows the items; use the arrow
keys to highlight a match and `Enter` to activate it. `DropdownMenuEmpty` shows (and is announced) once nothing matches.

<ComponentPreview
  name="dropdown-menu-filter-demo"
  description="Dropdown menu with a search field that filters grouped items, plus a filterable submenu."
/>

```tsx lineNumbers
<DropdownMenuFilterProvider>
  <DropdownMenu>
    <DropdownMenuTrigger render={<Button variant="outline" />}>Add component</DropdownMenuTrigger>
    <DropdownMenuContent className="w-64">
      <DropdownMenuInput aria-label="Search components" placeholder="Search components" />
      <DropdownMenuEmpty>No matching components.</DropdownMenuEmpty>
      <DropdownMenuList>
        <DropdownMenuGroup>
          <DropdownMenuLabel>Inputs</DropdownMenuLabel>
          <DropdownMenuItem>Kafka</DropdownMenuItem>
          <DropdownMenuItem>HTTP server</DropdownMenuItem>
        </DropdownMenuGroup>
      </DropdownMenuList>
    </DropdownMenuContent>
  </DropdownMenu>
</DropdownMenuFilterProvider>
```

- **Name the input.** Give `DropdownMenuInput` an `aria-label`, an `aria-labelledby`, or a visible `<label>`.
- **Automatic highlighting.** Opt in with `autoHighlight` to highlight the first match while typing. By default,
  arrow-key navigation passes through the input at either end of the list, showing its focus ring. Enabling
  `autoHighlight` skips that input step and wraps directly between items.
- **What matches.** An item matches when its text contains the query, ignoring case, accents and punctuation. Its text
  includes everything it renders, so set `label` on items with extra text such as a `DropdownMenuShortcut`:
  `<DropdownMenuItem label="Duplicate">Duplicate <DropdownMenuShortcut>⌘D</DropdownMenuShortcut></DropdownMenuItem>`.
- **Groups and separators.** A `DropdownMenuGroup` whose items all stop matching hides along with its
  `DropdownMenuLabel`, so keep labels inside groups. Separators hide while a query is typed and come back once it's
  cleared.
- **Submenus.** Wrap each `DropdownMenuSub` you want searchable in its own `DropdownMenuFilterProvider` and give its
  `DropdownMenuSubContent` an input and a list. Submenus without a provider stay unfiltered.
- **Controlling the query.** `value` and `onValueChange` on the provider control the query. It resets to an empty string
  when the menu closes, reported with the `popup-close` reason; call `eventDetails.cancel()` to keep it.
- **Custom matching.** Pass `filter` to replace the matching (`Menu.useFilter()` from `@base-ui/react/menu`
  gives the same locale-aware `contains` and `startsWith`), or `filter={null}` to filter the items
  yourself, for example from a server.

:::info
Menu filtering is a preview API in Base UI 1.9, so its details can still change in a Base UI minor release.
:::

#### Migrating from automatic highlighting

`DropdownMenuFilterProvider` previously defaulted `autoHighlight` to `true`; it now defaults to `false`.
Typing filters the list without highlighting an item, so press an arrow key before `Enter` to activate a match.
To preserve the previous type-then-Enter behavior, pass `autoHighlight` explicitly:

```tsx
<DropdownMenuFilterProvider autoHighlight>{/* Existing menu, input, and list */}</DropdownMenuFilterProvider>
```

### Triggers

#### Polymorphic link trigger

<ComponentPreview
  name="dropdown-menu-link-trigger-demo"
  description="A link trigger opens native menu links to real project-summary sections in this example."
/>

The example uses stable `project-navigation` destination IDs, so bookmarks and modified-click navigation
resolve in a fresh page too. When rendering multiple copies, pass a distinct, stable `id` to each
`DropdownMenuLinkTriggerDemo`, such as `primary-project-navigation` and `secondary-project-navigation`.
Do not generate bookmark destinations with `useId`: those identities depend on the rendered tree. Normal
fragment navigation stays on the current page and focuses the native destination; no custom navigation
or focus handler is needed.

#### Avatar user menu

<ComponentPreview
  name="dropdown-menu-avatar-trigger-demo"
  description="User menu with an avatar trigger, email header row, and sign-out action."
/>

### Opening dialogs from menu items

A menu item that opens a `Dialog`, `AlertDialog`, or `Sheet` is a common pattern but a nuanced composition. The
recommended shape: render the dialog as a **sibling** of `DropdownMenu` and share `open` state via consumer state.
Avoid rendering the dialog as a descendant of `DropdownMenuContent` — the menu's portal unmounts on close and tears
the dialog down before it reaches a stable open state.

Closing a menu can unmount its descendants before a nested dialog has completed its own lifetime. Keep the
owner mounted, or put the dialog outside the closing menu. Do not compose a dialog trigger that changes a menu
item into a button; use the menu item's `onClick` to change controlled dialog state.

The demo shows two supported compositions:

1. **Keep-mounted descendant** — `<DropdownMenuContent keepMounted>` retains its subtree when the menu closes,
   allowing the nested dialog to remain open. Prefer the sibling pattern when retaining that DOM is unnecessary. Pass the
   menu-trigger ref to the dialog popup's `finalFocus`, because the invoking menu item is hidden after the menu closes.
2. **Recommended sibling** — the dialog is a sibling of `DropdownMenu`, opened from a real menu item's `onClick`
   through shared state. The menu's portal can unmount independently.

<ComponentPreview
  name="dropdown-menu-nested-dialog-demo"
  description="Two supported ways to open a confirmation from a menu: a keep-mounted descendant and the recommended sibling dialog."
/>

### Animation

#### Open on hover

<ComponentPreview
  name="dropdown-menu-hover-demo"
  description="Dropdown menu that opens on hover with configurable delay."
/>

## Props

<div className="flex flex-row gap-x-3">
  <ExternalLink href="https://base-ui.com/react/components/menu" text="Docs" />
  <ExternalLink href="https://base-ui.com/react/components/menu#api-reference" text="API Reference" />
</div>

### Props

#### DropdownMenuFilterProvider

| Prop | Type | Default | Description |
| - | - | - | - |
| `autoHighlight?` | `boolean \| 'always'` | `false` | Highlight the first matching item while filtering, so Enter activates it. `'always'` highlights it even when the query is empty. Enabling this skips the input in the arrow-key loop. |
| `defaultValue?` | `string` | `''` | The query when the menu first renders. To control it, use `value` instead. |
| `value?` | `string` | - | The query, when controlled. Reset to an empty string when the menu closes unless you cancel that change. |
| `onValueChange?` | `(value: string, eventDetails: Menu.FilterProvider.ChangeEventDetails) => void` | - | Called when the query changes. `eventDetails.reason` is `'input-change'`, `'input-clear'`, `'clear-press'` or `'popup-close'`. |
| `filter?` | `((text: string, query: string) => boolean) \| null` | - | Replaces the matching. Receives each item’s `label` (or rendered text) and the trimmed query. Pass `null` to render filtered items yourself. |
| `locale?` | `Intl.LocalesArgument` | - | The locale the default matching compares strings in. Defaults to the user’s runtime locale. |

`DropdownMenuInput`, `DropdownMenuEmpty` and `DropdownMenuList` take their Base UI part’s props
([Input](https://base-ui.com/react/components/menu#input), [Empty](https://base-ui.com/react/components/menu#empty),
[List](https://base-ui.com/react/components/menu#list)), plus `testId` on the input and the empty state.
`DropdownMenuInput` and `DropdownMenuEmpty` need a `DropdownMenuFilterProvider`; `DropdownMenuList` also works in a plain
menu.

#### DropdownMenuSubTrigger

| Prop | Type | Default | Description |
| - | - | - | - |
| `inset?` | `boolean` | `false` | Whether the dropdown menu sub trigger is inset |

#### DropdownMenuContent

| Prop | Type | Default | Description |
| - | - | - | - |
| `keepMounted?` | `boolean` | `false` | Keep the menu's portal subtree mounted across close cycles. Use when state inside the menu must outlive close (open dialogs, in-flight confirmations). Trades the close animation for survival of the subtree. Avoid on lists with many rows × menu-per-row — prefer rendering dialogs as siblings of DropdownMenu instead. |

#### DropdownMenuItem

Use action items for commands, and `DropdownMenuLinkItem` for navigation. An action item's advertised
`render` prop is forwarded to Base UI, preserving its rendered host, refs, state callbacks, and handlers.

| Prop | Type | Default | Description |
| - | - | - | - |
| `inset?` | `boolean` | `false` | Whether the dropdown menu item is inset |
| `padding?` | `'standard' \| 'comfortable'` | `standard` | Standard menu-row padding, or comfortable padding for icon and avatar rows |
| `variant?` | `'default' \| 'destructive'` | `default` | The variant of the dropdown menu item |

#### DropdownMenuLinkItem

Accepts native anchor props and the installed Base UI `Menu.LinkItem` composition API, plus the same
`inset` and `padding` inputs as `DropdownMenuItem`. The default `padding` is `standard`; `comfortable`
provides the owned spacing for avatar and icon rows. The default `closeOnClick` is `false`.

#### DropdownMenuLabel

| Prop | Type | Default | Description |
| - | - | - | - |
| `variant?` | `'default' \| 'muted' \| 'identity'` | `default` | Default section heading, muted small heading, or an unpadded identity header |
| `inset?` | `boolean` | `false` | Whether the dropdown menu label is inset |

## Keyboard interactions

| Key                                                      | Action                                                                           |
| -------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `Enter` / `Space` / `ArrowDown` on `DropdownMenuTrigger` | Opens the menu and focuses the first item.                                       |
| `ArrowUp` / `ArrowDown`                                  | Moves between items. Wraps at the edges.                                         |
| `ArrowLeft` / `ArrowRight`                               | Opens / closes submenus.                                                         |
| `Home` / `End`                                           | Jumps to first / last item.                                                      |
| `Enter` / `Space`                                        | Activates the focused item.                                                      |
| `Escape`                                                 | Closes the menu and restores focus to the trigger.                               |
| Typing a character                                       | Moves focus to the next item whose label starts with that character (typeahead). |

In a filterable menu, opening it with a click or the keyboard focuses `DropdownMenuInput`, and typing filters instead of
jumping by typeahead. `ArrowUp` / `ArrowDown` move the highlight while the input keeps focus, `Enter` activates the
highlighted item, `Tab` closes the menu, and `Shift` + `Tab` returns focus to the trigger. Opening on hover or touch
leaves the input unfocused, so the on-screen keyboard stays hidden.

By default, pressing `ArrowDown` after the last item or `ArrowUp` before the first returns the highlight to the input,
where a focus ring marks the editing step. `Home` / `End` move the caret when the query has text; with an empty query
and a highlighted item, they jump to the first / last item. Modified arrow keys keep their native text-editing behavior.

## Accessibility

- `DropdownMenuTrigger` exposes `aria-haspopup="menu"` and `aria-expanded`; the menu itself is `role="menu"` with
  `role="menuitem"` / `menuitemcheckbox` / `menuitemradio` on its children.
- Focus is fully roving inside the menu — no `tabIndex={0}` needed on items.
- A filterable menu's popup is a `dialog` that holds the `searchbox` input and the `menu` list, and its trigger exposes
  `aria-haspopup="dialog"`. `DropdownMenuEmpty` is a polite live region. The clear button is pointer-only: it's
  hidden from assistive tech and the tab order, since deleting the text does the same.
- Destructive items should set `variant="destructive"` (visual) and, if the action is irreversible, open an
  `AlertDialog` for confirmation rather than firing immediately.
- The trigger must be a button (or another interactive element via the `render` prop); static `<div>` triggers lose keyboard
  activation.

## Controlled vs uncontrolled

Uncontrolled — typical case:

```tsx lineNumbers
<DropdownMenu>
  <DropdownMenuTrigger render={<Button onClick={() => undefined}>Actions</Button>} />
  <DropdownMenuContent>
    <DropdownMenuItem onClick={onEdit}>Edit</DropdownMenuItem>
    <DropdownMenuItem onClick={onDelete}>Delete</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>
```

Controlled — drive `open` from parent state (e.g. to open programmatically from a keyboard shortcut):

```tsx lineNumbers
const [open, setOpen] = useState(false);

<DropdownMenu open={open} onOpenChange={setOpen}>
  <DropdownMenuTrigger render={<Button onClick={() => undefined}>Actions</Button>} />
  <DropdownMenuContent>{/* ... */}</DropdownMenuContent>
</DropdownMenu>;
```

## Credits

- We use [Base UI](https://base-ui.com/react/components/menu) for the dropdown menu component.
- We take our inspiration from [Shadcn UI](https://ui.shadcn.com/docs/components/dropdown-menu) for the dropdown menu style.

## Sidebar menu surfaces

Use `variant="sidebar"` on `DropdownMenuContent` for organization, account, and
project menus in a sidebar. The owned recipe provides their larger corner radius.
Use `variant="destructive"` on destructive `DropdownMenuItem` actions rather than
overriding their text color; the recipe also owns highlighted and icon states.
