Skip to content
SproutAceve · v3.23.1
Get started
Overview What is your role? Connect to Sprout Adopting Sprout
Foundations
Brand & logos Color Typography Spacing & layout Elevation Iconography Accessibility
Components
Overview Buttons Inputs & forms Numeric input Data grid Ribbon toolbar Navigation Cards Alert Avatar Badge Dialog Tabs Floating tabs Menu
Practice
Interaction & motion Voice & content Product patterns
Artificial Intelligence
AI patterns AI transparency & disclosure AI disclosure label
People & ethics
Design principles Personas Trust and ethics Meet the makers
Playground
Build the systemplay Tower of tokensplay
Behind the system
How it works Changelogv3.23 Roadmap Contribute
Draft
Components

Menu.

Beta — usable, but the API may still change.

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.

Secondary actionsUse when the item has a primary action elsewhere and the menu surfaces the extras — rename, share, archive.
One objectEvery command in the list acts on the same target: the row, the cell, or the node under the cursor.
Three items or moreFewer than three commands usually fit a button group. The menu's discovery cost is only worth paying when the list is long enough to justify hiding it.
Never the only routeEvery command must also be reachable another way — toolbar, detail panel. Keyboard users who miss the trigger must not be blocked.
Not navigationMenus are for commands, not for jumping between pages. Navigation belongs in the ribbon or a dedicated nav component.
Consistent triggerPick one trigger per surface: right-click everywhere, or overflow "⋮" everywhere. Mixing the two hides commands from users who don't right-click.

Variants

Two compositions exist today. Nested submenus and a scrolling long-list treatment are not yet built.

Divider and menu labelsSeparate groups of related commands with a menu label above and a divider between. The destructive action always goes last.
Multi-selectAdd a checkbox to items when the user must pick more than one before acting — a "Select all" toggle usually goes at the top.
Menu item optionsEach row can carry a leading icon (consistent across all rows), a checkbox (role="menuitemcheckbox"), or a trailing star toggle.
Edit
Rename
Duplicate
File
Share
Delete row
Divider and menu labels
✓
Rename
Duplicate
✓
Share
Multi-select

States

Hover and focus are CSS states in production — shown here frozen for documentation.

DefaultItem visible and actionable. Label at standard weight, no background tint.
HoveredBackground shifts to accent-tint, label at medium weight. In production: :hover and :focus-visible.
SelectedActive / keyboard-focused item. Accent-tint background, label at medium weight. Same visual as hovered — represents the focused row during keyboard navigation.
DisabledItem visible but not actionable. Opacity 0.4. WCAG exempts disabled controls — prefer hiding over disabling wherever possible.
Rename
Default
Rename
Hovered
Rename
Selected
Rename
Disabled

Anatomy

Icon + label rows, a divider, and the destructive action last.

Rename
Duplicate
Share
Delete row
Surface220 px default width, var(--radius-2), var(--shadow-pop), 6 px padding
Menu item38 px height · 8 px top/bottom · 10 px sides · 13.5 px text
Menu label11 px, uppercase, 0.06em tracking, var(--ink-4) — not focusable
Icon, checkbox, star14–15 px, aligned in a single leading column across all rows
Divider1 px var(--line), 5 px 4 px margin — separates groups
Destructive itemvar(--danger) text · always last · after a divider · semantically last in DOM

Do & don't

Three rules on content — labels, icons, and grouping. Trigger placement rules live at the surface level, not here.

Rename
Duplicate
Share
Delete row
Do

Start every row with an imperative verb, and add the object when it isn't obvious. Sentence case, no full stop — the same rule Sprout applies to buttons.

OPTIONS
Copy everything to another location
Remove
Don't

Nouns like "Options" carry no information scent, ALL CAPS hurts legibility, and a sentence-long label wraps. "Remove" leaves people guessing what disappears.

Rename
Duplicate
Share
Do

If you use icons, use them on every row, same size, in a single leading column — always with the text label. The aligned column is what makes the list scannable.

Rename
Duplicate
Don't

Icons on some rows only breaks the alignment column. An icon without a text label is a guessing game — NN/g: icons must always carry a label.

Edit
Rename
Duplicate
Share
Delete row
Do

Keep the menu to commands that act on the object under the cursor. Group them with a menu label, separate groups with a divider, destructive action last.

Rename
Export project
Print all
Delete row
Don't

Export project and Print all are global commands — they belong in the ribbon. A destructive row mid-list invites mis-clicks.

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.

Figma propertyReact propNotes
Show Iconicon?: IconName14 px leading icon — use on all rows or none
Show Checkboxrole="menuitemcheckbox" + checked?: booleanSets aria-checked; pair with a controlled state
Show Starstarred?: booleanTrailing favourite toggle
Statedisabled?: boolean + CSS :hover/:focus-visibledisabled maps to a prop; hover and focus are CSS-only
type 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
← PreviousFloating tabsNext →Interaction & motion
Sprout · Aceve Design System · v3.23.1
Figma library GitLabReleases