DropdownMenu
A list of actions or toggles opened from a button.
Last action: nothing yet
import { Button, DropdownMenu } from "@merid/react";
export function Example() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Button variant="secondary">Actions</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item trailing="⌘E" onSelect={() => edit()}>Edit</DropdownMenu.Item>
<DropdownMenu.Item trailing="⌘D" onSelect={() => duplicate()}>Duplicate</DropdownMenu.Item>
<DropdownMenu.Item disabled>Move to…</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onSelect={() => archive()}>Archive</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
}Import
import { DropdownMenu } from "@merid/react";Anatomy
<DropdownMenu.Root>
<DropdownMenu.Trigger />
<DropdownMenu.Content>
<DropdownMenu.Label />
<DropdownMenu.Group>
<DropdownMenu.Item />
<DropdownMenu.CheckboxItem />
</DropdownMenu.Group>
<DropdownMenu.Separator />
</DropdownMenu.Content>
</DropdownMenu.Root>- Root holds the open state. It renders no element.
- Trigger is a
<button>witharia-haspopup="menu". - Content is the portalled
role="menu"panel, positioned against the trigger. It only mounts while open. - Item runs an action and closes the menu. CheckboxItem toggles a setting and keeps the menu open.
- Label, Group and Separator organise longer menus.
Examples
Checkbox items
CheckboxItem works controlled (checked + onCheckedChange) or uncontrolled (defaultChecked). Choosing one does not close the menu.
const [grid, setGrid] = useState(true);
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Button variant="secondary">View</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Label>Display</DropdownMenu.Label>
<DropdownMenu.CheckboxItem checked={grid} onCheckedChange={setGrid}>
Show grid
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem defaultChecked>Snap to guides</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem>Show rulers</DropdownMenu.CheckboxItem>
</DropdownMenu.Content>
</DropdownMenu.Root>Placement
placement accepts any Floating UI placement ("top", "bottom-end", "right-start"…). The menu flips and shifts to stay in the viewport.
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Button variant="secondary">Opens above, aligned end</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content placement="top-end" sideOffset={8}>
<DropdownMenu.Item>Rename</DropdownMenu.Item>
<DropdownMenu.Item>Share</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>Keeping the menu open
onSelect receives a cancelable event. Call event.preventDefault() to keep the menu open after an item is chosen.
<DropdownMenu.Item onSelect={(event) => { event.preventDefault(); refresh(); }}>
Refresh
</DropdownMenu.Item>API reference
Root
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | – | Controlled open state. |
defaultOpen | boolean | false | Initial open state when uncontrolled. |
onOpenChange | (open: boolean) => void | – | Called when the open state should change. |
children | ReactNode | – | Trigger and Content. |
Trigger
Accepts every <button> attribute. type defaults to "button" (not set when asChild is used).
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render the single child element (e.g. your own `Button`) instead of a `<button>`, merging props, ref and handlers. |
ref | Ref<HTMLButtonElement> | – | Forwarded ref to the button. |
Content
Accepts every <div> attribute.
| Prop | Type | Default | Description |
|---|---|---|---|
placement | Placement | "bottom-start" | Preferred placement relative to the trigger. |
sideOffset | number | 6 | Distance from the trigger in px. |
container | Element | null | document.body | Portal target. `undefined` uses `document.body`; `null` renders nothing until the target exists. |
ref | Ref<HTMLDivElement> | – | Forwarded ref to the menu element. |
Item
Accepts every <div> attribute except onSelect. Your onClick, onKeyDown and onPointerMove run first; call event.preventDefault() in them to skip the built-in behaviour (activation, highlighting).
| Prop | Type | Default | Description |
|---|---|---|---|
onSelect | (event: Event) => void | – | Called when the item is chosen by click, Enter or Space. Call preventDefault to keep the menu open. |
disabled | boolean | false | Disable the item; it is skipped by arrow keys. |
textValue | string | – | Text used for typeahead when children are not plain text. |
leading | ReactNode | – | Icon or element shown before the label. |
trailing | ReactNode | – | Shortcut hint or element shown after the label. |
ref | Ref<HTMLDivElement> | – | Forwarded ref to the item element. |
CheckboxItem
Accepts every <div> attribute except onSelect, with the same handler composition as Item. leading is not rendered; the check mark takes its place.
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | – | Controlled checked state. |
defaultChecked | boolean | false | Initial checked state when uncontrolled. |
onCheckedChange | (checked: boolean) => void | – | Called with the new checked state. The menu stays open. |
disabled | boolean | false | Disable the item; it is skipped by arrow keys. |
textValue | string | – | Text used for typeahead when children are not plain text. |
trailing | ReactNode | – | Shortcut hint or element shown after the label. |
ref | Ref<HTMLDivElement> | – | Forwarded ref to the item element. |
Group, Label, Separator
All three accept every <div> attribute. Group renders role="group"; give it an aria-label. Label is presentational text; Separator renders role="separator".
Styling
| Class | Element |
|---|---|
.mrd-menu | Content panel |
.mrd-menu__item | Item and CheckboxItem |
.mrd-menu__leading, .mrd-menu__trailing | Leading icon, trailing hint |
.mrd-menu__label-text | Item text |
.mrd-menu__check | CheckboxItem check slot |
.mrd-menu__label | Label |
.mrd-menu__separator | Separator |
| Attribute | On | Values |
|---|---|---|
data-state | Trigger (or the asChild element) | open, closed |
data-state | Content | open |
data-state | CheckboxItem | checked, unchecked |
data-disabled | Item, CheckboxItem | present when disabled |
Accessibility
Follows the WAI-ARIA Menu Button pattern. The trigger carries aria-haspopup="menu", aria-expanded and aria-controls; the menu is labelled by the trigger. Focus moves into the menu on open and returns to the trigger on Escape or selection.
| Key | Action |
|---|---|
| Enter | On the trigger: open and focus the first item. On an item: activate it. |
| Space | On the trigger: open and focus the first item. On an item: activate it. |
| ArrowDown | On the trigger: open on the first item. In the menu: next item, wrapping. |
| ArrowUp | On the trigger: open on the last item. In the menu: previous item, wrapping. |
| Home | First item. |
| End | Last item. |
| A–Z | Typeahead to the next item starting with the typed text. |
| Escape | Close and return focus to the trigger. |
| Tab | Close the menu and move focus on. |
Guidelines
Do
Use a menu for secondary actions on an object — rename, duplicate, archive — and keep labels to a verb or short phrase.
Avoid
Use a menu to pick a form value. Choosing one value from a list is a Select.
Do
Put destructive actions last, after a separator.
Avoid
Hide the only way to reach a primary action inside a menu.