Menu
Composable nested dropdown and context menus.
The Menu primitive is a modular dropdown navigation and option-selection menu component that supports keyboard accessibility, nested submenus, search indexing, and selection checkboxes.
Quick Start
Compose a fully accessible dropdown actions menu:
import { Menu, Button } from 'boltdocs/primitives'
export default function FileActions() {
return (
<Menu.Trigger placement="bottom start">
<Button className="px-4 py-2 bg-primary-500 text-white rounded-lg">
Actions
</Button>
<Menu className="p-1 bg-surface border border-subtle rounded-lg shadow-lg w-48 flex flex-col gap-0.5">
<Menu.Item
onAction={() => alert('New document created!')}
className="px-3 py-1.5 text-xs text-body hover:bg-soft rounded cursor-default"
>
New File
</Menu.Item>
<Menu.Item
onAction={() => alert('Document copied!')}
className="px-3 py-1.5 text-xs text-body hover:bg-soft rounded cursor-default"
>
Duplicate File
</Menu.Item>
<Menu.Separator className="border-t border-subtle my-1" />
<Menu.Item
onAction={() => alert('Document deleted!')}
className="px-3 py-1.5 text-xs text-danger-500 hover:bg-danger-500/10 rounded cursor-default"
>
Delete File
</Menu.Item>
</Menu>
</Menu.Trigger>
)
}
Composable Subcomponents
Compose rich selection menus and nested layout trees using the following namespaces:
| Component | HTML Tag | Description | Props |
|---|---|---|---|
Menu.Root / Menu | <div> (menu) | Wrapper that contains the menu item list viewport. | MenuProps |
Menu.Trigger | Wrapper | Wraps the trigger button and the menu popup overlay. | MenuTriggerProps |
Menu.SubTrigger | Wrapper | Wraps parent trigger items and nested child submenu layers. | SubmenuTriggerProps |
Menu.Item | <div> (menuitem) | An individual clickable item within the menu selection list. | MenuItemProps |
Menu.Section | <div> | Groups a collection of items under a specific section label. | MenuSectionProps |
Menu.Separator | <hr> | Visual separator dividing option categories. | SeparatorProps |
Component Props
MenuProps
Inherits the standard React Aria MenuProps settings:
| Property | Type | Default | Description |
|---|---|---|---|
onAction | (key: Key) => void | undefined | Callback invoked when any non-disabled item is clicked/selected. |
className | string | undefined | Custom CSS classes. |
MenuTriggerProps
Inherits React Aria MenuTriggerProps plus:
| Property | Type | Default | Description |
|---|---|---|---|
placement | 'bottom' | 'top' | 'left' | 'right' | 'start' | 'end' | 'bottom' | Alignment position relative to the trigger. |
MenuItemProps
Inherits standard React Aria MenuItemProps (e.g., isDisabled, onAction):
| Property | Type | Default | Description |
|---|---|---|---|
textValue | string | undefined | Equivalent text value matching the item for keyboard search autocomplete. |
check | ReactNode | (default check) | Custom multi-select check indicator. Replaces the default check. |
chevron | ReactNode | (default chevron) | Custom submenu chevron. Replaces the default chevron. |
checkClassName | string | undefined | Extra classes for the multi-select check slot. |
chevronClassName | string | undefined | Extra classes for the submenu chevron. |
contentClassName | string | undefined | Extra classes for the item content wrapper. |
MenuSectionProps
| Property | Type | Default | Description |
|---|---|---|---|
title | string | undefined | Section label header text. |
Customization & states
Menu is built on React Aria's Menu, so it ships the interaction handling (keyboard navigation, selection, submenus) without visuals. Styling lives in the theme: the Menu.Root and every Menu.Item accept a render-function className, and RAC mirrors the selection and focus states onto each item as data-* attributes for CSS targeting.
| State / Slot | Mechanism |
|---|---|
Menu.Item className (function) | Receives { isHovered, isFocused, isFocusVisible, isPressed, isSelected, isDisabled } |
Menu.Item children (function) | Receives { selectionMode, isSelected, hasSubmenu } |
data-selected | Present on the item when selected (checkbox selection mode) |
data-focused / data-focus-visible | Present on keyboard-focused items |
data-hovered / data-pressed | Pointer interaction states |
data-disabled | Present on disabled items |
Menu.Root className (function) | Receives { isEmpty } |
Tailwind v4:
<Menu.Item
className={({ isSelected, isFocused }) =>
`px-3 py-1.5 text-xs rounded cursor-default ${
isSelected
? 'bg-primary-500/10 text-primary-400'
: isFocused
? 'bg-soft text-body'
: 'text-body'
}`
}
>
Toggle Option
</Menu.Item>