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 StartLink

Compose a fully accessible dropdown actions menu:

tsx
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 SubcomponentsLink

Compose rich selection menus and nested layout trees using the following namespaces:

ComponentHTML TagDescriptionProps
Menu.Root / Menu<div> (menu)Wrapper that contains the menu item list viewport.MenuProps
Menu.TriggerWrapperWraps the trigger button and the menu popup overlay.MenuTriggerProps
Menu.SubTriggerWrapperWraps 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 PropsLink

Inherits the standard React Aria MenuProps settings:

PropertyTypeDefaultDescription
onAction(key: Key) => voidundefinedCallback invoked when any non-disabled item is clicked/selected.
classNamestringundefinedCustom CSS classes.

Inherits React Aria MenuTriggerProps plus:

PropertyTypeDefaultDescription
placement'bottom' | 'top' | 'left' | 'right' | 'start' | 'end''bottom'Alignment position relative to the trigger.

Inherits standard React Aria MenuItemProps (e.g., isDisabled, onAction):

PropertyTypeDefaultDescription
textValuestringundefinedEquivalent text value matching the item for keyboard search autocomplete.
checkReactNode(default check)Custom multi-select check indicator. Replaces the default check.
chevronReactNode(default chevron)Custom submenu chevron. Replaces the default chevron.
checkClassNamestringundefinedExtra classes for the multi-select check slot.
chevronClassNamestringundefinedExtra classes for the submenu chevron.
contentClassNamestringundefinedExtra classes for the item content wrapper.
PropertyTypeDefaultDescription
titlestringundefinedSection label header text.

Customization & statesLink

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 / SlotMechanism
Menu.Item className (function)Receives { isHovered, isFocused, isFocusVisible, isPressed, isSelected, isDisabled }
Menu.Item children (function)Receives { selectionMode, isSelected, hasSubmenu }
data-selectedPresent on the item when selected (checkbox selection mode)
data-focused / data-focus-visiblePresent on keyboard-focused items
data-hovered / data-pressedPointer interaction states
data-disabledPresent on disabled items
Menu.Root className (function)Receives { isEmpty }

Tailwind v4:

tsx
<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>
Last updated on October 18, 2026

Was this page helpful?