Menu
MD3 dropdown menu anchored to a trigger. It renders in the top layer, below
the trigger (flipping above when there is no room) and clamped to the
viewport, so ancestors' overflow or z-index never clip it.
Basic usage
trigger is a single button-like element; the menu wires up its click,
keyboard and ARIA attributes. The menu closes on item selection, outside
click, Escape and Tab-out.
<Menu trigger={<Button variant="outlined">Open menu</Button>}>
<MenuItem startIcon={<EditIcon />} onClick={handleEdit}>Edit</MenuItem>
<MenuItem startIcon={<CopyIcon />}>Copy</MenuItem>
<MenuDivider />
<MenuItem disabled>Delete</MenuItem>
</Menu>
startIcon / endIcon are decorative. MenuDivider renders a separator.
Alignment and controlled state
align="end" aligns the menu to the trigger's end edge — handy for an
overflow button. Control the open state with open + onOpenChange, or set
an initial state with defaultOpen.
const [open, setOpen] = useState(false)
<Menu
align="end"
open={open}
onOpenChange={setOpen}
trigger={<IconButton icon={<MoreVertIcon />} aria-label="More options" />}
>
<MenuItem>Settings</MenuItem>
</Menu>
Vertical menu (Expressive)
variant="vertical" is the MD3 Expressive vertical menu: segmented
MenuGroup containers (with an optional section label), 44dp items, and a
selected state. Setting selected (even to false) makes an item a
menuitemcheckbox; a selected item morphs to a 12dp corner shape.
color="vibrant" switches to the tertiary-based, higher-emphasis colors.
<Menu variant="vertical" color="vibrant" trigger={<Button>Format</Button>}>
<MenuGroup label="Format">
<MenuItem selected={bold} startIcon={<BoldIcon />} onClick={() => setBold(!bold)}>
Bold
</MenuItem>
</MenuGroup>
<MenuGroup>
<MenuItem startIcon={<CopyIcon />}>Copy</MenuItem>
</MenuGroup>
</Menu>
Accessibility
Follows the WAI-ARIA APG menu-button pattern:
- The trigger gets
aria-haspopup/aria-expanded; the popup isrole="menu"withmenuitem(ormenuitemcheckbox) items androle="group"for labelledMenuGroups. - Enter / Space / ↓ on the trigger open the menu and focus the first item (↑: the last). ↑ / ↓ cycle with wrap, Home / End jump, and typing a character moves focus by typeahead.
- Escape and item activation close the menu and return focus to the trigger.
- Items use a roving tabindex, so the menu never adds Tab stops to the page.
Disabled items stay focusable but inert (
aria-disabled).
Props
Menu
| Prop | Type | Default | Description |
|---|---|---|---|
align | enum | start | Horizontal alignment to the trigger. |
children * | ReactNode | — | `MenuItem`s — or, for the `vertical` variant, `MenuGroup`s. |
color | enum | standard | Color option for the `vertical` variant: `'standard'` (surface based) or `'vibrant'` (tertiary based, higher emphasis). |
defaultOpen | boolean | false | Uncontrolled initial open state. |
onOpenChange | ((open: boolean) => void) | — | Notified when the open state should change. |
open | boolean | — | Controlled open state. |
trigger * | ReactElement<unknown, string | JSXElementConstructor<any>> | — | The element that opens the menu (a single button-like element). |
variant | enum | standard | `'standard'` is the baseline dropdown menu; `'vertical'` is the MD3 Expressive vertical menu (segmented `MenuGroup` containers, 44dp items, selected states with shape morph). |
MenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
endIcon | ReactNode | — | Icon at the end of the item (decorative). |
selected | boolean | — | Selection state (for the menu's `vertical` variant). When set (even to `false`) the item becomes a `role="menuitemcheckbox"` with `aria-checked`; a selected item shows the tertiary-container mapping and morphs to a 12dp corner shape. |
startIcon | ReactNode | — | Icon at the start of the item (decorative). |
MenuGroup
| Prop | Type | Default | Description |
|---|---|---|---|
children * | ReactNode | — | `MenuItem`s (and `MenuDivider`s). |
label | ReactNode | — | Optional section label rendered above the group's items. |