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.
Links
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
Menu
| Prop | Type | Default |
|---|---|---|
childrenThe contents of the collection. | TypeReactNode | ((item: T) => ReactNode) | Default— |
classNameThe CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the | TypeClassNameOrFunction<MenuRenderProps> | Default— |
defaultSelectedKeysThe initial selected keys in the collection (uncontrolled). | TypeIterable<Key> | "all" | Default— |
dependenciesValues that should invalidate the item cache when using dynamic collections. | Typereadonly any[] | Default— |
disabledKeysThe item keys that are disabled. These items cannot be selected, focused, or otherwise | TypeIterable<Key> | Default— |
disallowEmptySelectionWhether the collection allows empty selection. | Typeboolean | Default— |
escapeKeyBehaviorWhether pressing the escape key should clear selection in the menu or not. | Type"clearSelection" | "none" | Default'clearSelection' |
itemsItem objects in the collection. | TypeIterable<T> | Default— |
renderEmptyStateProvides content to display when there are no items in the menu. | Type(() => ReactNode) | Default— |
selectedKeysThe currently selected keys in the collection (controlled). | TypeIterable<Key> | "all" | Default— |
selectionModeThe type of selection that is allowed in the collection. | Type"none" | "single" | "multiple" | Default— |
shouldCloseOnSelectWhether the menu should close when the menu item is selected. | Typeboolean | Default— |
shouldFocusWrapWhether keyboard navigation is circular. | Typeboolean | Default— |
size`md` is the compact menu. `lg` has taller items with room for a framed | Type"md" | "lg" | Defaultmd |
MenuItem
| Prop | Type | Default |
|---|---|---|
childrenThe children of the component. A function may be provided to alter the children based on | TypeChildrenOrFunction<MenuItemRenderProps> | Default— |
classNameThe CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the | TypeClassNameOrFunction<MenuRenderProps> | Default— |
externalArrowShows a small up-right arrow after `MenuItemLabel` and, on an item with | Typeboolean | Defaultfalse |
hrefA URL to link to. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href). | Typestring | Default— |
idThe unique id of the item. | TypeKey | Default— |
isDisabledWhether the item is disabled. | Typeboolean | Default— |
relThe relationship between the linked resource and the current page. See | Typestring | Default— |
shouldCloseOnSelectWhether the menu should close when the menu item is selected. | Typeboolean | Default— |
targetThe target window for the link. See | TypeHTMLAttributeAnchorTarget | Default— |
textValueA string representation of the item's contents, used for features like typeahead. | Typestring | Default— |
variant | Type"default" | "destructive" | null | Defaultdefault |
MenuItemIcon
Takes the props of a span; it has none of its own.
MenuItemLabel
| Prop | Type | Default |
|---|---|---|
elementType | Typestring | Default— |
MenuItemDescription
| Prop | Type | Default |
|---|---|---|
elementType | Typestring | Default— |
MenuSection
| Prop | Type | Default |
|---|---|---|
childrenThe contents of the collection. | TypeReactNode | ((item: T) => ReactNode) | Default— |
classNameThe CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the | TypeClassNameOrFunction<MenuRenderProps> | Default— |
defaultSelectedKeysThe initial selected keys in the collection (uncontrolled). | TypeIterable<Key> | "all" | Default— |
dependenciesValues that should invalidate the item cache when using dynamic collections. | Typereadonly any[] | Default— |
disallowEmptySelectionWhether the collection allows empty selection. | Typeboolean | Default— |
itemsItem objects in the collection. | TypeIterable<T> | Default— |
selectedKeysThe currently selected keys in the collection (controlled). | TypeIterable<Key> | "all" | Default— |
selectionModeThe type of selection that is allowed in the collection. | Type"none" | "single" | "multiple" | Default— |
shouldCloseOnSelectWhether the menu should close when the menu item is selected. | Typeboolean | Default— |
MenuHeader
Takes the props of React Aria's Header, a header element; it has none of its own.
MenuSeparator
| Prop | Type | Default |
|---|---|---|
classNameThe CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the | Typestring | Default— |
elementTypeThe HTML element type that will be used to render the separator. | Typestring | Default— |
orientationThe 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.