Dropdown menu · Menu item · Context menu
Dropdown menu — a list of actions that opens from a button, a pill or a right-click.
Beta · Navigation and overlay · packages/css/src/components/dropdown-menu.css · React on Base UI: @pepperui/dropdown-menu
.pui-dropdown-menu (__header __name __role __group-label __separator) · .pui-menu-item (--submenu --check --destructive; __icon __label __shortcut __chevron __check; data-highlighted, aria-disabled, aria-checked).
Sizes across, states down.
Dropdown menu — Beta
Section titled “Dropdown menu — Beta”- Has header + Name / Role (the user menu’s head); Has group label + Group label (a heading over a group of rows); Has separator; Has item 4 / 5 / 6
- Items 1–6 are exposed Menu items — label, icon, shortcut, Type (submenu · check · destructive) and State on each
- 220 wide; the height follows the rows; a submenu is a second Dropdown menu to the right, top-aligned to its row
Use when three or more actions hang off one control — a row’s ⋯, a File or Export button, the user pill, a right-click on a table row (Context menu: the same menu at the pointer).
Do not use when the list picks a value (Select, Combobox); one or two actions (Buttons, an Icon button); the content is a form (Popover); it is site navigation (Navigation menu).
Relates to Menu item, Context menu, Select menu, Popover, Kbd, Icon button - ghost, Topbar, Data table.
Anti-patterns more than one destructive row · a menu without a trigger · a two-level submenu · a menu that scrolls (split it — a notification list is the exception and may scroll) · a fill changed on the instance.
Keywords: dropdown menu, menu, actions, context menu, user menu, file menu, submenu
Figma — page Dropdown menu: Dropdown menu (1 variants) · Menu item (12 variants)
shadcn: dropdown-menu
Context menu — Beta
Section titled “Context menu — Beta”Dropdown menu — a list of actions that opens from a button, a pill or a right-click.
- Has header + Name / Role (the user menu’s head); Has group label + Group label (a heading over a group of rows); Has separator; Has item 4 / 5 / 6
- Items 1–6 are exposed Menu items — label, icon, shortcut, Type (submenu · check · destructive) and State on each
- 220 wide; the height follows the rows; a submenu is a second Dropdown menu to the right, top-aligned to its row
Use when three or more actions hang off one control — a row’s ⋯, a File or Export button, the user pill, a right-click on a table row (Context menu: the same menu at the pointer).
Do not use when the list picks a value (Select, Combobox); one or two actions (Buttons, an Icon button); the content is a form (Popover); it is site navigation (Navigation menu).
Relates to Menu item, Context menu, Select menu, Popover, Kbd, Icon button - ghost, Topbar, Data table.
Anti-patterns more than one destructive row · a menu without a trigger · a two-level submenu · a menu that scrolls (split it — a notification list is the exception and may scroll) · a fill changed on the instance.
Keywords: dropdown menu, menu, actions, context menu, user menu, file menu, submenu
the same Figma parts as dropdown-menu, opened at the pointer; Base UI ContextMenu on the code side
Figma — page Dropdown menu: Dropdown menu (1 variants) · Menu item (12 variants)
shadcn: context-menu
The markup — copy this
Section titled “The markup — copy this”The class, the parts, and nothing that is not in the package. The same block opens dropdown-menu.css.
<div class="pui-dropdown-menu" role="menu"> <div class="pui-dropdown-menu__header"><p class="pui-dropdown-menu__name">the design lead</p><p class="pui-dropdown-menu__role">Owner</p></div> <div class="pui-dropdown-menu__group-label">Deck</div> <button class="pui-menu-item" role="menuitem"><span class="pui-icon pui-menu-item__icon">…</span>Duplicate <kbd class="pui-kbd pui-menu-item__shortcut">⌘D</kbd></button> <button class="pui-menu-item pui-menu-item--submenu" role="menuitem">Export<span class="pui-icon pui-menu-item__chevron">…</span></button> <div class="pui-dropdown-menu__separator" role="separator"></div> <button class="pui-menu-item pui-menu-item--destructive" role="menuitem">Delete</button></div>The modifiers
Section titled “The modifiers”| What it is | Note | |
|---|---|---|
| Item Type | (none) default · --submenu · --check · --destructive | — |
| Item State | :hover, :focus, [data-highlighted] share the surface, and KEYBOARD focus adds the inset ring on top of it — the shared surface alone is 1.12 against the menu and WCAG 2.4.13 asks 3:1 · [aria-disabled=“true”] | — |
| Parts | __icon · __label · __shortcut · __chevron · __check (shown on [aria-checked=“true”]) | — |
Accessibility — measured, not assumed
Section titled “Accessibility — measured, not assumed”Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.
Contrast
Section titled “Contrast”| ink on ground | reads | floor | the matrix calls it | |
|---|---|---|---|---|
#e42521 on #ffffff |
4.57 | 4.5 | text.error on surface.page (promised) | ok |
#544d4d on #f2f2f2 |
7.36 | 4.5 | text.muted on surface.sunken (promised) | ok |
#544d4d on #ffffff |
8.24 | 4.5 | text.muted on surface.page (promised) | ok |
#0b0202 on #f2f2f2 |
18.32 | 4.5 | text.default on surface.sunken (promised) | ok |
#0b0202 on #ffffff |
20.51 | 4.5 | text.default on surface.page (promised) | ok |
5 distinct pair(s) rendered, 0 that axe calls a violation.
The focus ring
Section titled “The focus ring”| primitive | on | the indicator | over | the change | |
|---|---|---|---|---|---|
| Menu item | surface/page | shadow-inset #6a66e4 2 px |
#ffffff |
4.53 | ok |
8 of 8 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured.
Keyboard
Section titled “Keyboard”| role | tab stops | what the pattern requires | |
|---|---|---|---|
menu .pui-dropdown-menu |
0 | 0 — its trigger holds it | ok |
menu .pui-dropdown-menu |
0 | 0 — its trigger holds it | ok |
What the consumer’s script owes. NO tab stop at rest — the trigger holds it. Every item takes tabindex=“-1” and the script moves focus to the first on open. It owes Up / Down, Home / End, Enter / Space, Escape (which closes and returns focus to the trigger), Right / Left for a submenu, and typeahead by first letter.
Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).
Pepper UI v1.5.0
The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.