Skip to content

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

DefaultOpen in Storybook ↗

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

PropTypeDescription
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>;

Examples

With Label
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuLabel>{t("actions")}</DropdownMenuLabel>
<DropdownMenuItem onClick={duplicate}>{t("duplicate")}</DropdownMenuItem>
<DropdownMenuItem disabled>{t("archive")}</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>;
Open in Storybook ↗
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>;
Open in Storybook ↗