Persona

Dropdown Menu

Overlay · 9 props · Updated Sep 22, 2026

A dropdown menu is a list of actions on a surface that opens from a trigger you supply. The trigger is a required prop rather than a part of the menu, so the same component serves an overflow icon, an avatar or a button. Dropdown Button is this component with the button already supplied.

  • Trigger the element that opens the menu, and the element the menu positions itself against. It is a required prop rather than a part of the surface.
  • Menu (when open) the elevated surface that holds the list, anchored to the trigger.
  • Section heading (optional, when open) labels a group of related items; it is not itself selectable.
  • Menu item (when open) one action. Destructive items take the danger variant and go last.
  • Divider (optional, when open) separates groups of items without naming them.
1 of 5 parts falls below the 4.5:1 WCAG AA threshold for normal text in the dark theme only (△).

A dropdown menu holds actions, not choices. Choosing an item runs something and the menu closes; nothing in it stays selected, and there is no value to read back. When the reader is picking a value that persists, use a Select or a Combobox, which have somewhere to put it.

Use it for the secondary and contextual actions that do not need to sit in view all the time — the overflow beside a table row, the account menu behind an avatar. Navigation between views is not one of those: put it in a Tab Bar, where the reader can see where they are without opening anything.

For a surface holding a paragraph and a control or two rather than a list of actions, see Popover, which sets out the boundary between the three surfaces that open over a page.

A menu opens under the pointer, so the item nearest the trigger is the one the reader reaches by accident. Putting Delete at the bottom costs a deliberate movement to get to, and that movement is the only protection an item has before it fires.

Label the item for what it destroys rather than leaving the risk to color. variant="danger" on DropdownMenu.Item turns the row red, and red is not a warning to anyone who cannot see it. The props table below covers the menu itself; the items take their own set, including variant, prefixIcon, subtitle and disabled.

Seven items is the working limit. A menu has no search and no scroll position, so a long one is worked through from the top every time it opens. Split it with DropdownMenu.Divider, or move the whole set somewhere with room.

DropdownMenu.Heading labels a group visually and does nothing else: it renders as a list item with no ARIA role and no association to the items beneath it, so a screen reader reads the heading as one more line of text rather than as the name of a group. Prefer a divider where the grouping has to be understood by everyone.

The menu measures itself against the trigger element and takes its placement entirely from it. side picks the edge and align the alignment along that edge; align defaults to end, which is why an unconfigured menu hangs from the trigger’s right.

portal decides where the menu lives in the document, which is a different question from where it appears. It defaults to false, so the menu renders inside the trigger’s own subtree — turn it on when an ancestor with overflow: hidden or a stacking context clips the surface.

With no children, DropdownMenu returns nothing at all, and that includes the trigger. A menu built from a filtered list can therefore make its own button disappear when the filter empties, which reads as a broken page rather than an empty menu. Render the trigger yourself with a disabled state when there is a chance of having nothing to offer.

disabled is the softer version: it keeps the trigger and drops the menu, so clicking opens nothing.

modal is forced off, so the page keeps scrolling and everything behind the menu stays clickable. That is what a menu should do, because a menu is a detour and not an interruption, and it is the line between this and Modal.

Every association a menu needs is already in the markup. Measured on this page’s own specimen: the trigger carries aria-haspopup="menu", aria-expanded and aria-controls; the surface carries role="menu" and an aria-labelledby pointing back at the trigger; each item carries role="menuitem"; each divider carries role="separator". The keyboard behavior underneath is Radix’s menu implementation: arrow keys, Home, End, Escape and type-ahead, with focus moving into the menu on open and back to the trigger on close.

So setting any of these by hand either duplicates what is there or overwrites something correct. The one thing left to you is the trigger’s own accessible name, which is an ordinary Button problem when the trigger is an icon.

Leave out of the item what the trigger already says

Section titled “Leave out of the item what the trigger already says”

The trigger stays on screen while the menu is open, so the reader can see what the menu belongs to. “Delete” beside a row already means delete that row, and “Delete this candidate’s record” spends a line repeating something already on the screen.

That only holds while the trigger says what it is about. An overflow icon says nothing, so its items carry the subject themselves.

Capitalization, punctuation and the rules that apply to every label are on Writing.

NameType
id

string

className

string

triggerrequired

FunctionComponentElement<any>

contentProps

DropdownMenuContentProps

Props passed to internal DropdownMenu.Content component.

triggerProps

DropdownMenuTriggerProps

Props passed to internal DropdownMenu.Trigger component.

portalProps

DropdownMenuPortalProps

Props passed to internal DropdownMenu.Portal component.

portal

boolean

Whether or not to wrap menu with DropdownMenu.Portal component.

disabled

boolean

Whether or not clicks render menu items.

zIndex

ZIndex

Also accepts every prop of Primitive.DropdownMenuProps and Pick<Primitive.DropdownMenuContentProps, ‘side’ | ‘align’>.