DropdownMenu
Generic dropdown menu primitive built on react-aria-components — a list of
actions anchored to a trigger. Token-driven and domain-agnostic.
import { DropdownMenu } from "@evolve-storefront/ui/dropdown-menu";Design notes
Built on RAC's MenuTrigger rather than a hand-rolled open/close pair, because
a menu is the one overlay whose keyboard contract is too big to reimplement:
open on ArrowDown, roving focus, typeahead, Escape, and focus restored to the
trigger. That is also why DropdownMenuTrigger takes button variants instead
of asChild — RAC has to own the trigger to wire any of it up.
Preview
Usage
Also the identity contract for a trigger that never passes through Button.
<DropdownMenu> <DropdownMenuTrigger variant="secondary">{t("actions")}</DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuItem onClick={rename}> <Icon name="edit" /> {t("rename")} </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem destructive onClick={remove}> <Icon name="remove" /> {t("delete")} </DropdownMenuItem> </DropdownMenuContent></DropdownMenu>;Props
| Prop | Type | Description |
|---|---|---|
open? | boolean | Controlled open state. Omit to let the menu own it. |
defaultOpen? | boolean | |
onOpenChange? | ((open: boolean) => void) | |
children? | ReactNode |
Change the layout
Pass children to DropdownMenu to arrange its parts yourself. Every part is exported from @evolve-storefront/ui/dropdown-menu.
<DropdownMenu> <DropdownMenuTrigger variant="secondary">{t("actions")}</DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuItem onClick={rename}> <Icon name="edit" /> {t("rename")} </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem destructive onClick={remove}> <Icon name="remove" /> {t("delete")} </DropdownMenuItem> </DropdownMenuContent></DropdownMenu>;DropdownMenuContentThe menu panel, anchored to the trigger.
| Prop | Type | Description |
|---|---|---|
placement? | DropdownMenuPlacement | |
offset? | number | Gap between trigger and menu, in px. |
children? | ReactNode |
Any other props go to the root element, like on a plain HTML element.
DropdownMenuGroupA group of items that a DropdownMenuLabel inside it names.
No props of its own.
DropdownMenuItemOne action in the menu.
| Prop | Type | Description |
|---|---|---|
onClick? | (() => void) | Invoked when the item is chosen — by click, Enter, or Space. |
disabled? | boolean | |
destructive? | boolean | Style the item as a destructive action (e.g. delete). |
textValue? | string | Announced instead of the children when the label is not plain text. |
Any other props go to the root element, like on a plain HTML element.
DropdownMenuLabelA heading for the DropdownMenuGroup it sits in.
No props of its own.
DropdownMenuSeparatorA line between two groups of items.
No props of its own.
DropdownMenuTriggerThe button that opens the menu.
| Prop | Type | Description |
|---|---|---|
variant? | ButtonVariant | |
tone? | ButtonTone | |
isIconOnly? | boolean | The button has no visible label, so it is square. Structural rather than a size: it says the button has nothing to be wide for, and says nothing about how big — the brand's control tokens still decide that. MoreNot inferred from the children. A kebab menu is as often a glyph or an SVG
as an |
data-component? | undefined | Not a caller's to rename — |
Any other props go to the root element, like on a plain HTML element.
Examples
With Label
<DropdownMenuContent> <DropdownMenuGroup> <DropdownMenuLabel>{t("actions")}</DropdownMenuLabel> <DropdownMenuItem onClick={duplicate}>{t("duplicate")}</DropdownMenuItem> <DropdownMenuItem disabled>{t("archive")}</DropdownMenuItem> </DropdownMenuGroup></DropdownMenuContent>;Kebab (icon) trigger
// No visible label, so the accessible name goes on the trigger.<DropdownMenuTrigger variant="tertiary" isIconOnly aria-label={t("openMenu")}> <Icon name="more" /></DropdownMenuTrigger>;
