Menu

A list of actions or links revealed from a trigger, in a compact size and a large size with framed icons and descriptions.

Menu collects actions that act on a single object — a row, a card, a selection — behind one trigger. At size="lg" rows have a framed icon and a description, and a highlight slides between them; base.org's navigation dropdown is one example. For choosing a value from a set rather than firing an action, use a Select; for switching between peer views of one object, use Tabs.

MenuTrigger takes the trigger followed by a Popover that holds the Menu; the popover handles positioning and dismissal.

import { MenuTrigger } from 'react-aria-components';
import { Button } from '@base-org/bds-web/product/button';
import {
  Menu,
  MenuHeader,
  MenuItem,
  MenuItemDescription,
  MenuItemIcon,
  MenuItemLabel,
  MenuSection,
  MenuSeparator,
} from '@base-org/bds-web/product/menu';
import { Popover } from '@base-org/bds-web/product/popover';

MenuTrigger comes from react-aria-components; BDS does not re-export it.

Sizes

Medium

The default. Compact 14px rows with an 8px gap, filled gray on hover and keyboard focus.

<MenuTrigger>
<Button variant="outline">Actions</Button>
<Popover>
  <Menu>
    <MenuItem>Edit</MenuItem>
    <MenuItem>Duplicate</MenuItem>
    <MenuItem variant="destructive">Delete</MenuItem>
  </Menu>
</Popover>
</MenuTrigger>

Large

size="lg" is for rows with a framed icon and a description, for example a site navigation menu like base.org's. Rows have 6px of padding (24px at the end), a 16px gap and a 12px radius; the label is fg-primary. Instead of each row filling on hover, one gray highlight slides to the hovered or keyboard-focused row. With reduced motion it jumps without animating.

<Menu size="lg" aria-label="Build">
<MenuItem href="/get-started" textValue="Get Started">
  <MenuItemIcon><RocketIcon /></MenuItemIcon>
  <MenuItemLabel>Get Started</MenuItemLabel>
  <MenuItemDescription>Launch your first app</MenuItemDescription>
</MenuItem>
<MenuItem href="/ecosystem" textValue="Ecosystem">
  <MenuItemIcon><GlobeIcon /></MenuItemIcon>
  <MenuItemLabel>
    Ecosystem <Badge variant="label">New</Badge>
  </MenuItemLabel>
  <MenuItemDescription>Apps built on Base</MenuItemDescription>
</MenuItem>
<MenuItem href="https://docs.base.org" externalArrow textValue="Docs">
  <MenuItemIcon><DocumentIcon /></MenuItemIcon>
  <MenuItemLabel>Docs</MenuItemLabel>
  <MenuItemDescription>Guides and references</MenuItemDescription>
</MenuItem>
</Menu>

The size comes from the Menu; items and slots restyle themselves from it, so a MenuItem takes no size of its own.

Variants

Default

Gray-80 text in a medium menu, fg-primary in a large one.

Destructive

Give a destructive item variant="destructive" so it reads as dangerous before it is selected. Its text and hover fill (or highlight) turn red.

Item content

Icons

MenuItemIcon holds a leading icon. In a medium menu it is a bare 20px icon. In a large menu it is a 24px icon in a 1px bordered frame with a 6px radius, gray-50 until the item is hovered or focused, then brand blue. The icon is decorative and hidden from assistive technology; the slot sizes it, so pass the icon without a size.

Descriptions

MenuItemLabel names the item and MenuItemDescription adds a muted 14px line under it. With a description, the label and description stack beside the icon. Use both slots together, so the label alone names the item and the description is announced as its description.

Badge and external arrow

Put a Badge with variant="label" inside MenuItemLabel to flag a new destination. externalArrow adds a small up-right arrow after the label and, on an item with href and no target, opens the link in a new tab.

Give a MenuItem an href and it renders a link, so navigation menus keep link semantics and open-in-new-tab behaviour.

Sections and separators

MenuSection groups related items; a MenuHeader inside it is a footnote-size uppercase subheader that labels the group. MenuSeparator draws a 1px divider between items or sections. It runs the full width of the menu, past the items' edge to the menu's padding edge.

<Menu size="lg" aria-label="Resources">
<MenuSection>
  <MenuHeader>Learn</MenuHeader>
  <MenuItem href="/blog">Blog</MenuItem>
  <MenuItem href="/guides">Guides</MenuItem>
</MenuSection>
<MenuSeparator />
<MenuSection>
  <MenuHeader>Community</MenuHeader>
  <MenuItem href="https://github.com/base" externalArrow textValue="GitHub">
    <MenuItemLabel>GitHub</MenuItemLabel>
  </MenuItem>
</MenuSection>
</Menu>

List item styles

Menu items and Select items share listItemStyles, which takes a size (md or lg, default md) and a variant (default or destructive, default default). Select uses md. A large row draws no hover fill of its own, because Menu supplies the sliding highlight.

When to use

Use Menu to collect actions that act on one object — a row, a card, a selection — behind a single trigger, or, at size="lg", for a site navigation dropdown of links.

Use something else when:

  • The reader is picking a value. Use Select.
  • The reader is moving between views. Use Tabs.
  • It is the primary action of the view. Put a Button on the surface.
  • It is a single link in running text. Use a Link.

Usage

Do

Keep items to short verb phrases that act on the trigger's object — "Rename", "Move to…", "Delete".

Don't

Use a menu for the primary action of a view. If an action matters enough to be seen, put it on the surface as a Button.

Do

Keep descriptions to one short line. In a large menu, give every item an icon or none of them, so the labels line up.

Don't

Mix sizes in one menu, or use a large menu for row actions on a product surface.

When an item's children are not a plain string, give it a textValue so typeahead can find it.

Inside a Popover, a long menu takes the popover's available height and scrolls without a scrollbar; the clipped edge fades instead.

Props

PropTypeDefault
children

The contents of the collection.

TypeReactNode | ((item: T) => ReactNode)Default—
className

The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the

TypeClassNameOrFunction<MenuRenderProps>Default—
defaultSelectedKeys

The initial selected keys in the collection (uncontrolled).

TypeIterable<Key> | "all"Default—
dependencies

Values that should invalidate the item cache when using dynamic collections.

Typereadonly any[]Default—
disabledKeys

The item keys that are disabled. These items cannot be selected, focused, or otherwise

TypeIterable<Key>Default—
disallowEmptySelection

Whether the collection allows empty selection.

TypebooleanDefault—
escapeKeyBehavior

Whether pressing the escape key should clear selection in the menu or not.

Type"clearSelection" | "none"Default'clearSelection'
items

Item objects in the collection.

TypeIterable<T>Default—
renderEmptyState

Provides content to display when there are no items in the menu.

Type(() => ReactNode)Default—
selectedKeys

The currently selected keys in the collection (controlled).

TypeIterable<Key> | "all"Default—
selectionMode

The type of selection that is allowed in the collection.

Type"none" | "single" | "multiple"Default—
shouldCloseOnSelect

Whether the menu should close when the menu item is selected.

TypebooleanDefault—
shouldFocusWrap

Whether keyboard navigation is circular.

TypebooleanDefault—
size

`md` is the compact menu. `lg` has taller items with room for a framed

Type"md" | "lg"Defaultmd
PropTypeDefault
children

The children of the component. A function may be provided to alter the children based on

TypeChildrenOrFunction<MenuItemRenderProps>Default—
className

The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the

TypeClassNameOrFunction<MenuRenderProps>Default—
externalArrow

Shows a small up-right arrow after `MenuItemLabel` and, on an item with

TypebooleanDefaultfalse
href

A URL to link to. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href).

TypestringDefault—
id

The unique id of the item.

TypeKeyDefault—
isDisabled

Whether the item is disabled.

TypebooleanDefault—
rel

The relationship between the linked resource and the current page. See

TypestringDefault—
shouldCloseOnSelect

Whether the menu should close when the menu item is selected.

TypebooleanDefault—
target

The target window for the link. See

TypeHTMLAttributeAnchorTargetDefault—
textValue

A string representation of the item's contents, used for features like typeahead.

TypestringDefault—
variantType"default" | "destructive" | nullDefaultdefault

Takes the props of a span; it has none of its own.

PropTypeDefault
elementTypeTypestringDefault—
PropTypeDefault
elementTypeTypestringDefault—
PropTypeDefault
children

The contents of the collection.

TypeReactNode | ((item: T) => ReactNode)Default—
className

The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the

TypeClassNameOrFunction<MenuRenderProps>Default—
defaultSelectedKeys

The initial selected keys in the collection (uncontrolled).

TypeIterable<Key> | "all"Default—
dependencies

Values that should invalidate the item cache when using dynamic collections.

Typereadonly any[]Default—
disallowEmptySelection

Whether the collection allows empty selection.

TypebooleanDefault—
items

Item objects in the collection.

TypeIterable<T>Default—
selectedKeys

The currently selected keys in the collection (controlled).

TypeIterable<Key> | "all"Default—
selectionMode

The type of selection that is allowed in the collection.

Type"none" | "single" | "multiple"Default—
shouldCloseOnSelect

Whether the menu should close when the menu item is selected.

TypebooleanDefault—

Takes the props of React Aria's Header, a header element; it has none of its own.

PropTypeDefault
className

The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the

TypestringDefault—
elementType

The HTML element type that will be used to render the separator.

TypestringDefault—
orientation

The orientation of the separator.

Type"horizontal" | "vertical"Default'horizontal'

Accessibility

Menu wraps React Aria's Menu, so it exposes role="menu", moves focus with the arrow keys, supports typeahead, and closes on Escape or outside press. Each MenuItem is a menuitem reachable without a pointer; with href it is a link that keeps the menuitem role.

MenuItemLabel and MenuItemDescription are React Aria Text slots: the item is labelled by the label (aria-labelledby) and described by the description (aria-describedby). A Badge inside the label is part of the name ("Ecosystem New"). The icon frame and external arrow are aria-hidden.

MenuSection renders a group that its MenuHeader labels, and MenuSeparator renders a separator. The sliding highlight follows keyboard focus as well as the pointer.

To give every item in a region the same defaults — for example variant="destructive" for a block of dangerous actions — wrap them in MenuItemPropsContext; see Manual.