Dropdown Menu
Displays a menu of actions or functions when the user presses a trigger button, with groups, submenus, checkbox and radio items, and shortcuts.
Installation
npx shadcn@latest add @redpanda/dropdown-menupnpm dlx shadcn@latest add @redpanda/dropdown-menuyarn dlx shadcn@latest add @redpanda/dropdown-menubunx shadcn@latest add @redpanda/dropdown-menunubx shadcn@latest add @redpanda/dropdown-menuaube dlx shadcn@latest add @redpanda/dropdown-menuWhen to use
Use this decision tree to determine when to use the Dropdown Menu component:
Usage
<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
Radio group
Link items
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
Composition
Submenus
Labeled sections
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.
<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
DropdownMenuInputanaria-label, anaria-labelledby, or a visible<label>. - Automatic highlighting. Opt in with
autoHighlightto 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. EnablingautoHighlightskips 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
labelon items with extra text such as aDropdownMenuShortcut:<DropdownMenuItem label="Duplicate">Duplicate <DropdownMenuShortcut>⌘D</DropdownMenuShortcut></DropdownMenuItem>. - Groups and separators. A
DropdownMenuGroupwhose items all stop matching hides along with itsDropdownMenuLabel, so keep labels inside groups. Separators hide while a query is typed and come back once it’s cleared. - Submenus. Wrap each
DropdownMenuSubyou want searchable in its ownDropdownMenuFilterProviderand give itsDropdownMenuSubContentan input and a list. Submenus without a provider stay unfiltered. - Controlling the query.
valueandonValueChangeon the provider control the query. It resets to an empty string when the menu closes, reported with thepopup-closereason; calleventDetails.cancel()to keep it. - Custom matching. Pass
filterto replace the matching (Menu.useFilter()from@base-ui/react/menugives the same locale-awarecontainsandstartsWith), orfilter={null}to filter the items yourself, for example from a server.
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:
<DropdownMenuFilterProvider autoHighlight>{/* Existing menu, input, and list */}</DropdownMenuFilterProvider>Triggers
Polymorphic link trigger
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
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:
- 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’sfinalFocus, because the invoking menu item is hidden after the menu closes. - Recommended sibling — the dialog is a sibling of
DropdownMenu, opened from a real menu item’sonClickthrough shared state. The menu’s portal can unmount independently.
Animation
Open on hover
Props
Props
DropdownMenuFilterProvider
autoHighlight?boolean | 'always'
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.
boolean | 'always'falsedefaultValue?string
The query when the menu first renders. To control it, use `value` instead.
string''value?string
The query, when controlled. Reset to an empty string when the menu closes unless you cancel that change.
stringonValueChange?(value: string, eventDetails: Menu.FilterProvider.ChangeEventDetails) => void
Called when the query changes. `eventDetails.reason` is `'input-change'`, `'input-clear'`, `'clear-press'` or `'popup-close'`.
(value: string, eventDetails: Menu.FilterProvider.ChangeEventDetails) => voidfilter?((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.
((text: string, query: string) => boolean) | nulllocale?Intl.LocalesArgument
The locale the default matching compares strings in. Defaults to the user’s runtime locale.
Intl.LocalesArgumentDropdownMenuInput, DropdownMenuEmpty and DropdownMenuList take their Base UI part’s props
(Input, Empty,
List), plus testId on the input and the empty state.
DropdownMenuInput and DropdownMenuEmpty need a DropdownMenuFilterProvider; DropdownMenuList also works in a plain
menu.
DropdownMenuSubTrigger
inset?boolean
Whether the dropdown menu sub trigger is inset
booleanfalseDropdownMenuContent
keepMounted?boolean
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.
booleanfalseDropdownMenuItem
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.
inset?boolean
Whether the dropdown menu item is inset
booleanfalsepadding?'standard' | 'comfortable'
Standard menu-row padding, or comfortable padding for icon and avatar rows
'standard' | 'comfortable'standardvariant?'default' | 'destructive'
The variant of the dropdown menu item
'default' | 'destructive'defaultDropdownMenuLinkItem
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
variant?'default' | 'muted' | 'identity'
Default section heading, muted small heading, or an unpadded identity header
'default' | 'muted' | 'identity'defaultinset?boolean
Whether the dropdown menu label is inset
booleanfalseKeyboard 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
DropdownMenuTriggerexposesaria-haspopup="menu"andaria-expanded; the menu itself isrole="menu"withrole="menuitem"/menuitemcheckbox/menuitemradioon its children.- Focus is fully roving inside the menu — no
tabIndex={0}needed on items. - A filterable menu’s popup is a
dialogthat holds thesearchboxinput and themenulist, and its trigger exposesaria-haspopup="dialog".DropdownMenuEmptyis 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 anAlertDialogfor confirmation rather than firing immediately. - The trigger must be a button (or another interactive element via the
renderprop); static<div>triggers lose keyboard activation.
Controlled vs uncontrolled
Uncontrolled — typical case:
<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):
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 for the dropdown menu component.
- We take our inspiration from Shadcn UI 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.