Menu
The Menu component provide end users with a list of options on temporary surfaces.
Installation
Base UI components are all available as a single package.
npm install @base-ui-components/react
Once you have the package installed, import the component.
import { Menu } from '@base-ui-components/react/menu';
Anatomy
Menus are implemented using a collection of related components:
<Menu.Root />
is a top-level component that facilitates communication between other components. It does not render to the DOM.<Menu.Trigger />
is an optional component (a button by default) that, when clicked, shows the menu. When not used, menu can be shown programmatically using theopen
prop.<Menu.Positioner />
renders the element responsible for positioning the popup.<Menu.Popup />
is the menu popup.<Menu.Item />
is the menu item.<Menu.Arrow />
renders an optional pointing arrow, placed inside the popup.<Menu.SubmenuTrigger />
is a menu item that opens a submenu. See Nested menu for more details.<Menu.Group>
groups related items making assistive technologies announce them distinctively.<Menu.GroupLabel>
provides an accessible label for the Menu.Group.<Menu.Separator>
visually separates items.<Menu.RadioItem />
is a menu item that acts as a radio button. See Radio items for more details.<Menu.RadioGroup />
groups RadioItems together. Only one RadioItem in a group can be selected at a time.<Menu.MenuRadioItemIndicator />
is a visual indicator for the selected RadioItem.<Menu.CheckboxItem />
is a menu item that acts as a checkbox. See Checkbox items for more details.<Menu.CheckboxItemIndicator />
is a visual for the selected CheckboxItem.
<Menu.Root>
<Menu.Trigger />
<Menu.Positioner>
<Menu.Popup>
<Menu.Group>
<Menu.GroupLabel />
<Menu.Item />
<Menu.Item />
</Menu.Group>
<Menu.Separator />
<Menu.Root>
<Menu.SubmenuTrigger />
<Menu.Positioner>
<Menu.Popup>
<Menu.Arrow />
<Menu.Item />
<Menu.Item />
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
Placement
By default, the menu is placed on the bottom side of its trigger, the default anchor. To change this, use the side
prop:
<Menu.Root>
<Menu.Trigger />
<Menu.Positioner side="right">
<Menu.Popup>
<Menu.Item>Item 1</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
You can also change the align of the menu in relation to its anchor. By default, aligned to the leading edge of an anchor, but it can be configured otherwise using the align
prop:
<Menu.Root>
<Menu.Trigger />
<Menu.Positioner side="right" align="end">
<Menu.Popup>
<Menu.Item>Item 1</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
Due to collision detection, the menu may change its placement to avoid overflow. Therefore, your explicitly specified side
and align
props act as "ideal", or preferred, values.
To access the true rendered values, which may change as the result of a collision, the menu element receives data attributes:
// Rendered HTML (simplified)
<div>
<div data-side="left" data-align="end">
<div>Item 1</div>
</div>
</div>
This allows you to conditionally style the menu based on its rendered side or align.
Offset
The sideOffset
prop creates a gap between the anchor and menu popup, while alignOffset
slides the menu popup from its align, acting logically for start
and end
aligns.
<Menu.Positioner sideOffset={10} alignOffset={10}>
Orientation
By default, menus are vertical, so the up/down arrow keys navigate through options and left/right keys open and close submenus.
You can change this with the orientation
prop"
<Menu.Root orientation="horizontal">
<Menu.Trigger />
<Menu.Positioner>
<Menu.Popup>
<Menu.Item>Item 1</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
Hover
To open the Menu on hover, add the openOnHover
prop:
<Menu.Root openOnHover>
By default submenus are opened on hover, but top-level menus aren't.
Delay
To change how long the menu waits until it opens or closes when openOnHover
is enabled, use the delay
prop, which represent how long the Menu waits after the cursor enters the trigger, in milliseconds:
<Menu.Root openOnHover delay={200}>
Radio items
Menu items can be used as radio buttons. To group them together, use the Menu.RadioGroup
component:
If you rely on the RadioItem to manage its state (for example, you use the defaultChecked
and onCheckedChange
props), ensure that the item is not unmounted when its parent menu is closed.
Unmounting the component resets its state.
To do this, add the keepMounted
prop to the Menu.Positioner
the checkbox item is in (and all parent positioners, in the case of a nested menu):
<Menu.Root>
<Menu.Positioner keepMounted>
<Menu.Popup>
<Menu.RadioGroup>
<Menu.RadioItem defaultChecked>
<Menu.RadioItemIndicator /> Light
</Menu.RadioItem>
<Menu.RadioItem>
<Menu.RadioItemIndicator /> Dark
</Menu.RadioItem>
</Menu.RadioGroup>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
If you keep the state externally (and use the checked
prop), this isn't required.
Checkbox items
Menu items can act as checkboxes.
If you rely on the CheckboxItem to manage its state (for example, you use the defaultChecked
and onCheckedChange
props), ensure that the item is not unmounted when its parent menu is closed.
Unmounting the component resets its state.
To do this, add the keepMounted
prop to the Menu.Positioner
the checkbox item is in (and all parent positioners, in the case of a nested menu):
<Menu.Root>
<Menu.Positioner keepMounted>
<Menu.Popup>
<Menu.CheckboxItem defaultChecked>
<Menu.CheckboxItemIndicator /> Item 1
</Menu.CheckboxItem>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
If you keep the state externally (and use the checked
prop), this isn't required.
Nested menu
Menu items can open submenus.
To make this happen, place the <Menu.Root>
with all its required children where a submenu trigger has to be placed, but instead of <Menu.Trigger>
, use <Menu.SubitemTrigger>
, as on the demo below.
Escape key behavior
You can control if pressing the Escape key closes just the current submenu or the whole tree.
By default, the whole menu closes, but setting the closeParentOnEsc
prop modifies this behavior:
<Menu.Root>
<Menu.Trigger />
<Menu.Positioner>
<Menu.Popup>
<Menu.Item>Item 1</Menu.Item>
<Menu.Root closeParentOnEsc={false}>
<Menu.SubmenuTrigger>Submenu</Menu.SubmenuTrigger>
<Menu.Positioner>
<Menu.Popup>
<Menu.Item>Submenu item 1</Menu.Item>
<Menu.Item>Submenu item 2</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
</Menu.Popup>
</Menu.Positioner>
</Menu.Root>
Arrow
To add an arrow (caret or triangle) inside the menu popup that points toward the center of the anchor element, use the Menu.Arrow
component:
<Menu.Positioner>
<Menu.Popup>
<Menu.Arrow />
<Menu.Item>Item 1</Menu.Item>
<Menu.Item>Item 2</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
It automatically positions a wrapper element that can be styled or contain a custom SVG shape.
Separator
To visually separate items, use the Menu.Separator
component:
<Menu.Popup>
<Menu.Item>Item 1</Menu.Item>
<Menu.Separator />
<Menu.Item>Item 2</Menu.Item>
</Menu.Popup>
The Menu.Separator is a re-exported Separator component. See the Separator docs to learn about its API.
Animations
The menu can animate when opening or closing with either:
- CSS transitions
- CSS animations
- JavaScript animations
CSS transitions
Here is an example of how to apply a symmetric scale and fade transition with the default conditionally-rendered behavior:
<Menu.Popup className="MenuPopup">
<Menu.Item>Item 1</Menu.Item>
</Menu.Popup>
.MenuPopup {
transform-origin: var(--transform-origin);
transition-property: opacity, transform;
transition-duration: 0.2s;
}
/* Represents the initial styles when entering */
.MenuPopup[data-starting-style],
.MenuPopup[data-ending-style] {
opacity: 0;
transform: scale(0.9);
}
[data-starting-style]
and [data-ending-style]
are attributes that are added to the popup when it is first inserted to the DOM and when it is about to be removed, respectively. In these states, you can specify the initial and final styles of the popup, which for symmetric transitions should be the same.
In newer browsers, there is a feature called @starting-style
which allows transitions to occur on open for conditionally-mounted components:
/* Base UI API - Polyfill */
.MenuPopup[data-starting-style] {
opacity: 0;
transform: scale(0.9);
}
/* Official Browser API */
@starting-style {
.MenuPopup[data-open] {
opacity: 0;
transform: scale(0.9);
}
}
CSS animations
CSS animations can also be used, requiring only two separate declarations:
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.9);
}
}
@keyframes scale-out {
to {
opacity: 0;
transform: scale(0.9);
}
}
.MenuPopup {
animation: scale-in 0.2s forwards;
}
.MenuPopup[data-ending-style] {
animation: scale-out 0.2s forwards;
}
JavaScript animations
The keepMounted
prop lets an external library control the mounting, for example framer-motion
's AnimatePresence
component.
function App() {
const [open, setOpen] = useState(false);
return (
<Menu.Root open={open} onOpenChange={setOpen}>
<Menu.Trigger>Trigger</Menu.Trigger>
<AnimatePresence>
{open && (
<Menu.Positioner keepMounted>
<Menu.Popup
render={
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
}
>
<Menu.Item>Item 1</Menu.Item>
<Menu.Item>Item 2</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
)}
</AnimatePresence>
</Menu.Root>
);
}
Animation states
Four states are available as data attributes to animate the popup, which enables full control depending on whether the popup is being animated with CSS transitions or animations, JavaScript, or is using the keepMounted
prop.
[data-open]
-open
state istrue
.[data-starting-style]
- the popup was just inserted to the DOM. The attribute is removed 1 animation frame later.[data-ending-style]
- the popup's final styles once it closes.
Overriding default components
Use the render
prop to override the rendered elements with your own components.
// Element shorthand
<Menu.Popup render={<MyMenuPopup />} />
// Function
<Menu.Popup render={(props) => <MyMenuPopup {...props} />} />
API Reference
MenuItem
An unstyled menu item to be used within a Menu.
Prop | Type | Default | Description |
---|---|---|---|
closeOnClick | bool | true | If true , the menu will close when the menu item is clicked. |
disabled | bool | false | If true , the menu item will be disabled. |
id | string | The id of the menu item. | |
label | string | A text representation of the menu item's content. Used for keyboard text navigation matching. | |
onClick | func | The click handler for the menu item. |
MenuPositioner
Renders the element that positions the Menu popup.
Prop | Type | Default | Description |
---|---|---|---|
align | enum | 'center' | The align of the Menu element to the anchor element along its cross axis. |
alignOffset | number | 0 | The offset of the Menu element along its align axis. |
anchor | union | The anchor element to which the Menu popup will be placed at. | |
arrowPadding | number | 5 | Determines the padding between the arrow and the Menu popup's edges. Useful when the popover popup has rounded corners via border-radius . |
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
collisionBoundary | union | 'clipping-ancestors' | The boundary that the Menu element should be constrained to. |
collisionPadding | union | 5 | The padding of the collision boundary. |
container | union | The container element to which the Menu popup will be appended to. | |
keepMounted | bool | false | Whether the menu popup remains mounted in the DOM while closed. |
positionMethod | enum | 'absolute' | The CSS position strategy for positioning the Menu popup element. |
render | union | A function to customize rendering of the component. | |
side | enum | 'bottom' | The side of the anchor element that the Menu element should align to. |
sideOffset | number | 0 | The gap between the anchor element and the Menu element. |
sticky | bool | false | If true , allow the Menu to remain in stuck view while the anchor element is scrolled out of view. |
MenuPopup
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
id | string | The id of the popup element. | |
render | union | A function to customize rendering of the component. |
MenuRoot
Prop | Type | Default | Description |
---|---|---|---|
animated | bool | true | If true , the Menu supports CSS-based animations and transitions. It is kept in the DOM until the animation completes. |
closeParentOnEsc | bool | true | Determines if pressing the Esc key closes the parent menus. This is only applicable for nested menus. If set to false pressing Esc closes only the current menu. |
defaultOpen | bool | false | If true , the Menu is initially open. |
delay | number | 100 | The delay in milliseconds until the menu popup is opened when openOnHover is true . |
dir | enum | 'ltr' | The direction of the Menu (left-to-right or right-to-left). |
disabled | bool | false | If true , the Menu is disabled. |
loop | bool | true | If true , using keyboard navigation will wrap focus to the other end of the list once the end is reached. |
onOpenChange | func | Callback fired when the component requests to be opened or closed. | |
open | bool | Allows to control whether the dropdown is open. This is a controlled counterpart of defaultOpen . | |
openOnHover | bool | Whether the menu popup opens when the trigger is hovered after the provided delay . By default, openOnHover is set to true for nested menus. | |
orientation | enum | 'vertical' | The orientation of the Menu (horizontal or vertical). |
MenuTrigger
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
disabled | bool | false | If true , the component is disabled. |
label | string | Label of the button | |
render | union | A function to customize rendering of the component. |
SubmenuTrigger
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
disabled | bool | false | If true , the menu item will be disabled. |
label | string | A text representation of the menu item's content. Used for keyboard text navigation matching. | |
render | union | A function to customize rendering of the component. |
MenuArrow
Renders an arrow that points to the center of the anchor element.
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
hideWhenUncentered | bool | false | If true , the arrow is hidden when it can't point to the center of the anchor element. |
render | union | A function to customize rendering of the component. |
MenuRadioGroup
Prop | Type | Default | Description |
---|---|---|---|
children | node | The content of the component. | |
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
defaultValue | any | The default value of the selected radio button. This is the uncontrolled equivalent of value . | |
onValueChange | func | () => {} | Function called when the selected value changes. |
render | union | A function to customize rendering of the component. | |
value | any | The value of the selected radio button. |
MenuRadioItem
An unstyled radio menu item to be used within a Menu.
Prop | Type | Default | Description |
---|---|---|---|
value | any | Value of the radio item. This is the value that will be set in the MenuRadioGroup when the item is selected. | |
closeOnClick | bool | true | If true , the menu will close when the menu item is clicked. |
disabled | bool | false | If true , the menu item will be disabled. |
id | string | The id of the menu item. | |
label | string | A text representation of the menu item's content. Used for keyboard text navigation matching. | |
onClick | func | The click handler for the menu item. |
MenuRadioItemIndicator
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
keepMounted | bool | true | If true , the component is mounted even if the Radio is not checked. |
render | union | A function to customize rendering of the component. |
MenuCheckboxItem
An unstyled checkbox menu item to be used within a Menu.
Prop | Type | Default | Description |
---|---|---|---|
checked | bool | If true , the checkbox is checked. | |
closeOnClick | bool | true | If true , the menu will close when the menu item is clicked. |
defaultChecked | bool | false | The default checked state. Use when the component is uncontrolled. |
disabled | bool | false | If true , the menu item will be disabled. |
id | string | The id of the menu item. | |
label | string | A text representation of the menu item's content. Used for keyboard text navigation matching. | |
onCheckedChange | func | Callback fired when the checked state is changed. | |
onClick | func | The click handler for the menu item. |
MenuCheckboxItemIndicator
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
keepMounted | bool | true | If true , the component is mounted even if the checkbox is not checked. |
render | union | A function to customize rendering of the component. |
MenuGroup
Prop | Type | Default | Description |
---|---|---|---|
children | node | The content of the component. | |
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |
MenuGroupLabel
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |