Menu.
A short list of actions for one object, opened by right-click or by an overflow (“⋮”) button. Keep it to the commands that act on the thing under the cursor — anything global belongs in the ribbon, not here.
When to use
Menus are for object-scoped commands, not navigation and not system-wide actions.
Variants
Two compositions exist today. Nested submenus and a scrolling long-list treatment are not yet built.
States
Hover and focus are CSS states in production — shown here frozen for documentation.
Anatomy
Icon + label rows, a divider, and the destructive action last.
Do & don't
Three rules on content — labels, icons, and grouping. Trigger placement rules live at the surface level, not here.
Accessibility
Menus live on the keyboard — pointer is the shortcut, not the contract.
role="menu"On the surface element; labelled with aria-label describing what the menu acts on.role="menuitem"Each row; destructive items are visually and semantically last in the DOM.Arrow keysUp / Down move between items; Home / End jump to first / last.Esc closesEscape dismisses the menu; focus returns to the trigger element.TriggerRight-click or an overflow button with aria-haspopup="menu" and aria-expanded.Shift + F10Opens the context menu at keyboard focus without a mouse. Required for full keyboard coverage on Windows.Props (React)
Figma property names and React prop names are not the same thing — this table maps them.
Show Iconicon?: IconName14 px leading icon — use on all rows or noneShow Checkboxrole="menuitemcheckbox" + checked?: booleanSets aria-checked; pair with a controlled stateShow Starstarred?: booleanTrailing favourite toggleStatedisabled?: boolean + CSS :hover/:focus-visibledisabled maps to a prop; hover and focus are CSS-onlytype MenuProps = {
open: boolean;
anchor: HTMLElement | { x: number; y: number }; // trigger element or right-click point
placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end";
label: string; // aria-label — what the menu acts on
dense?: boolean; // tighter rows for long lists (proposal — not yet shipped)
onClose: (reason: "select" | "escape" | "outside") => void;
children: ReactNode; // MenuItem | MenuLabel | MenuDivider
};
type MenuItemProps = {
label: string; // imperative verb + noun, sentence case
icon?: IconName; // 14 px leading icon — all rows or none
role?: "menuitem" | "menuitemcheckbox" | "menuitemradio";
checked?: boolean; // sets aria-checked on checkbox / radio rows
starred?: boolean; // trailing favourite toggle
destructive?: boolean; // proposal — last item, after a divider
disabled?: boolean;
onSelect: () => void; // performs the command and closes the menu
};
type MenuLabelProps = { children: string }; // group heading, not focusable
type MenuDividerProps = {}; // separates groups