# Menu

> A list of actions opened from a trigger.

Source: /docs/components/menu

```tsx
import { Menu } from '@aksornproject/aksara-ui'
```

## Basic menu

`Menu` is a namespace of parts with the Base UI Menu names and nesting. Use it to run actions. To pick a value, use [Select](/docs/components/select).

`Menu.Positioner` places the panel below the trigger with the right edges aligned and an 8px gap. `Menu.Popup` is the 280px panel. Group items under a `Menu.GroupLabel` inside a `Menu.Group`, and divide them with `Menu.Separator`.

```tsx
import { Menu } from '@aksornproject/aksara-ui'

export default function MenuBasic() {
  return (
    <Menu.Root>
      <Menu.Trigger>Actions</Menu.Trigger>
      <Menu.Portal>
        <Menu.Positioner>
          <Menu.Popup>
            <Menu.Group>
              <Menu.GroupLabel>Homework</Menu.GroupLabel>
              <Menu.Item>Edit</Menu.Item>
              <Menu.Item>Duplicate</Menu.Item>
              <Menu.Item endContent="⌘S">Share</Menu.Item>
            </Menu.Group>
            <Menu.Separator />
            <Menu.Item destructive>Delete</Menu.Item>
          </Menu.Popup>
        </Menu.Positioner>
      </Menu.Portal>
    </Menu.Root>
  )
}
```

## Triggers

`Menu.Trigger` draws a labelled trigger with a chevron. `size` is `default` (44px tall) or `small` (36px). The label truncates at 240px. The chevron points down while closed and up while open. `chevron={false}` hides it.

`iconOnly` makes a square trigger with no label or chevron. It shows the dots icon unless a `leadingIcon` or children replace it. Give it an `aria-label`. `disabled` stops the trigger opening the menu.

```tsx
import { IconInterfaceEssentialSetting } from '@aksornproject/aksara-icons'
import { Menu, type MenuTriggerProps } from '@aksornproject/aksara-ui'

function ActionsMenu(trigger: MenuTriggerProps) {
  return (
    <Menu.Root>
      <Menu.Trigger {...trigger} />
      <Menu.Portal>
        <Menu.Positioner>
          <Menu.Popup>
            <Menu.Item>Edit</Menu.Item>
            <Menu.Item>Duplicate</Menu.Item>
          </Menu.Popup>
        </Menu.Positioner>
      </Menu.Portal>
    </Menu.Root>
  )
}

export default function MenuTriggers() {
  return (
    <>
      <ActionsMenu>Actions</ActionsMenu>
      <ActionsMenu size="small">Actions</ActionsMenu>
      <ActionsMenu iconOnly aria-label="More actions" />
      <ActionsMenu size="small" iconOnly aria-label="More actions" />
      <ActionsMenu leadingIcon={<IconInterfaceEssentialSetting />}>
        Settings
      </ActionsMenu>
      <ActionsMenu chevron={false}>No chevron</ActionsMenu>
      <ActionsMenu disabled>Disabled</ActionsMenu>
    </>
  )
}
```

## Items

Children are the label. It wraps and the row grows. `description` adds a second line. `leadingIcon` is a 16px slot. `endContent` holds a shortcut or similar and truncates at 60px. `destructive` is for delete actions only.

A disabled item stays focusable so keyboard users can find it, but it does not run. `Menu.LinkItem` renders the same row as an `<a>`.

`leadingIcon` and `endContent` take an element, a function or anything else. A single element becomes the slot and gets the slot classes and the state as `data-*` attributes. It must forward its props to the DOM. A function `(props, state) => element` gets the slot props to spread and the item state, `{ destructive, disabled }`. Anything else renders inside a slot `span`. The trigger `leadingIcon` works the same way with the trigger state, `{ open, disabled, size, iconOnly }`.

```tsx
import {
  IconInterfaceEssentialAttachmentLink,
  IconInterfaceEssentialCopy,
  IconInterfaceEssentialShare,
  IconInterfaceEssentialTrashDeleteBin2,
} from '@aksornproject/aksara-icons'
import { Menu } from '@aksornproject/aksara-ui'

export default function MenuItems() {
  return (
    <Menu.Root>
      <Menu.Trigger>Assignment</Menu.Trigger>
      <Menu.Portal>
        <Menu.Positioner>
          <Menu.Popup>
            <Menu.Item leadingIcon={<IconInterfaceEssentialCopy />}>
              Duplicate
            </Menu.Item>
            <Menu.Item
              leadingIcon={<IconInterfaceEssentialShare />}
              description="Anyone with the link can view"
            >
              Share
            </Menu.Item>
            <Menu.Item endContent="⌘E">Edit</Menu.Item>
            <Menu.Item disabled>Archive</Menu.Item>
            <Menu.LinkItem
              href="/docs/components/dialog"
              leadingIcon={<IconInterfaceEssentialAttachmentLink />}
            >
              Open the Dialog page
            </Menu.LinkItem>
            <Menu.Separator />
            <Menu.Item
              destructive
              leadingIcon={<IconInterfaceEssentialTrashDeleteBin2 />}
            >
              Delete
            </Menu.Item>
          </Menu.Popup>
        </Menu.Positioner>
      </Menu.Portal>
    </Menu.Root>
  )
}
```

## Checkbox and radio items

`Menu.CheckboxItem` draws the checkbox slot. `Menu.RadioItem` goes inside a `Menu.RadioGroup` and shows a tick when checked. Checked rows of both get the selection fill.

The radio tick sits at the end. `indicator="start"` moves it to the start. Pass `closeOnClick` to close the menu after a pick.

```tsx
import { Menu } from '@aksornproject/aksara-ui'
import { useState } from 'react'

export default function MenuSelection() {
  const [sort, setSort] = useState('newest')
  const [grid, setGrid] = useState(true)

  return (
    <Menu.Root>
      <Menu.Trigger>View</Menu.Trigger>
      <Menu.Portal>
        <Menu.Positioner>
          <Menu.Popup>
            <Menu.CheckboxItem checked={grid} onCheckedChange={setGrid}>
              Show grid
            </Menu.CheckboxItem>
            <Menu.Separator />
            <Menu.RadioGroup value={sort} onValueChange={setSort}>
              <Menu.GroupLabel>Sort by</Menu.GroupLabel>
              <Menu.RadioItem value="newest" closeOnClick>
                Newest
              </Menu.RadioItem>
              <Menu.RadioItem value="oldest" closeOnClick>
                Oldest
              </Menu.RadioItem>
              <Menu.RadioItem value="name" indicator="start" closeOnClick>
                Name
              </Menu.RadioItem>
            </Menu.RadioGroup>
          </Menu.Popup>
        </Menu.Positioner>
      </Menu.Portal>
    </Menu.Root>
  )
}
```

## Submenu

`Menu.SubmenuRoot` wraps a `Menu.SubmenuTrigger` and the `Menu.Portal` it opens. The trigger row shows a chevron. The nested `Menu.Positioner` opens the panel beside the row with the first rows lined up.

```tsx
import { Menu } from '@aksornproject/aksara-ui'

export default function MenuSubmenu() {
  return (
    <Menu.Root>
      <Menu.Trigger>Lesson</Menu.Trigger>
      <Menu.Portal>
        <Menu.Positioner align="start">
          <Menu.Popup>
            <Menu.Item>Edit</Menu.Item>
            <Menu.SubmenuRoot>
              <Menu.SubmenuTrigger>Move to</Menu.SubmenuTrigger>
              <Menu.Portal>
                <Menu.Positioner>
                  <Menu.Popup>
                    <Menu.Item>Unit 1</Menu.Item>
                    <Menu.Item>Unit 2</Menu.Item>
                    <Menu.Item>Archive</Menu.Item>
                  </Menu.Popup>
                </Menu.Positioner>
              </Menu.Portal>
            </Menu.SubmenuRoot>
          </Menu.Popup>
        </Menu.Positioner>
      </Menu.Portal>
    </Menu.Root>
  )
}
```

## Search

Wrap the menu in `Menu.FilterProvider` and put `Menu.Search` in the popup. Without the provider, Base UI throws an error that says so. Base UI hides the items that do not match. Pass `filter={null}` to the provider to filter the items yourself.

The search row stays above the list while the list scrolls. Put `Menu.Search` directly in `Menu.Popup` or inside a fragment. The arrow keys move the highlight while the field keeps focus. The clear button shows while the field has text.

```tsx
import { Menu } from '@aksornproject/aksara-ui'

const CLASSES = [
  'M.1/1',
  'M.1/2',
  'M.1/3',
  'M.2/1',
  'M.2/2',
  'M.3/1',
  'M.3/2',
  'M.3/3',
]

export default function MenuSearch() {
  return (
    <Menu.FilterProvider>
      <Menu.Root>
        <Menu.Trigger>Assign to class</Menu.Trigger>
        <Menu.Portal>
          <Menu.Positioner>
            <Menu.Popup>
              <Menu.Search />
              {CLASSES.map((name) => (
                <Menu.Item key={name}>{name}</Menu.Item>
              ))}
            </Menu.Popup>
          </Menu.Positioner>
        </Menu.Portal>
      </Menu.Root>
    </Menu.FilterProvider>
  )
}
```

## Scrolling

`maxHeight` on `Menu.Popup` caps the item list, in px or any CSS length. The list scrolls natively past it with an 8px gutter. Without it, the panel hugs its items up to the space the viewport has.

```tsx
import { Menu } from '@aksornproject/aksara-ui'

const WEEKS = Array.from({ length: 16 }, (_, index) => `Week ${index + 1}`)

export default function MenuScroll() {
  return (
    <Menu.Root>
      <Menu.Trigger>Jump to week</Menu.Trigger>
      <Menu.Portal>
        <Menu.Positioner>
          <Menu.Popup maxHeight={240}>
            {WEEKS.map((week) => (
              <Menu.Item key={week}>{week}</Menu.Item>
            ))}
          </Menu.Popup>
        </Menu.Positioner>
      </Menu.Portal>
    </Menu.Root>
  )
}
```

## Custom trigger

`render` replaces the built-in trigger with your element, such as a library `Button`. The trigger then adds only the behaviour and ARIA, with no classes or content. `render` cannot be combined with `size`, `iconOnly`, `leadingIcon` or `chevron`.

Here the `Button` keeps its focus ring while the menu is open through `forceFocusVisible`.

```tsx
import {
  IconInterfaceEssentialDots,
  IconInterfaceEssentialTrashDeleteBin2,
} from '@aksornproject/aksara-icons'
import { Button, Menu } from '@aksornproject/aksara-ui'
import { useState } from 'react'

export default function MenuRender() {
  const [open, setOpen] = useState(false)

  return (
    <Menu.Root open={open} onOpenChange={setOpen}>
      <Menu.Trigger
        render={(props) => (
          <Button
            {...props}
            iconOnly
            color="brand"
            variant="tertiary-accent"
            size="sm"
            aria-label="Grade actions"
            forceFocusVisible={open}
          >
            <IconInterfaceEssentialDots />
          </Button>
        )}
      />
      <Menu.Portal>
        <Menu.Positioner>
          <Menu.Popup>
            <Menu.Item
              destructive
              leadingIcon={<IconInterfaceEssentialTrashDeleteBin2 />}
            >
              Delete grade
            </Menu.Item>
          </Menu.Popup>
        </Menu.Positioner>
      </Menu.Portal>
    </Menu.Root>
  )
}
```

## Accessibility

- The trigger is a native button with `aria-haspopup="menu"` and `aria-expanded`. Give an icon-only trigger an `aria-label`.
- Items are `menuitem`, `menuitemcheckbox` or `menuitemradio`.
- Arrow keys, Home, End and typeahead move between items. Enter and Space run them, and Escape closes.
- `Menu.Search` is a searchbox named "Search". It sits outside the `menu` role. Its clear button stays out of the tab order and the accessibility tree.
- The menu is modal by default. Pass `modal={false}` to `Menu.Root` to keep the page scrollable and clickable while it is open.

## API

### Menu.Root

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultOpen` | `boolean` | `false` | Whether the menu is initially open. To render a controlled menu, use the `open` prop instead. |
| `loopFocus` | `boolean` | `true` | Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. |
| `highlightItemOnHover` | `boolean` | `true` | Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state. |
| `modal` | `boolean` | `true` | Determines if the menu enters a modal state when open. - `true`: user interaction is limited to the menu: document page scroll is locked and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. On touch devices, a `true` modal blocks outside taps but leaves the page scrollable unless the popup spans nearly the full viewport width, matching native iOS behavior. Nested menus ignore this prop, and menus opened by hover are never modal. |
| `onOpenChange` | `((open: boolean, eventDetails: MenuRoot.ChangeEventDetails) => void)` |  | Event handler called when the menu is opened or closed. |
| `onOpenChangeComplete` | `((open: boolean) => void)` |  | Event handler called after any animations complete when the menu is opened or closed. |
| `onItemHighlighted` | `((highlightedItem: HTMLElement \| undefined, eventDetails: MenuRoot.HighlightEventDetails) => void)` |  | Callback fired when an item is highlighted or unhighlighted. Receives the highlighted item element (or `undefined` if no item is highlighted) and details containing the reason for the change, the event, and the item's text label. The `reason` can be: - `'keyboard'`: the highlight changed due to keyboard navigation. - `'pointer'`: the highlight changed due to pointer hovering. The event may be a `MouseEvent` rather than a `PointerEvent`. - `'none'`: the highlight changed for another reason, such as automatic highlighting while filtering, the item list changing, or the popup opening or closing. |
| `open` | `boolean` |  | Whether the menu is currently open. |
| `orientation` | `"horizontal" \| "vertical"` | `'vertical'` | The visual orientation of the menu. Controls whether roving focus uses up/down or left/right arrow keys. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `closeParentOnEsc` | `boolean` | `false` | When in a submenu, determines whether pressing the Escape key closes the entire menu, or only the current child menu. |
| `actionsRef` | `React.RefObject<MenuRootActions \| null>` |  | A ref to imperative actions. - `unmount`: Ends the closing phase of the menu after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the menu completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the menu imperatively when called. |
| `triggerId` | `string \| null` |  | ID of the trigger that the menu is associated with. This is useful in conjunction with the `open` prop to create a controlled menu. There's no need to specify this prop when the menu is uncontrolled (that is, when the `open` prop is not set). |
| `defaultTriggerId` | `string \| null` |  | ID of the trigger that the menu is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open menu. |
| `handle` | `MenuHandle<unknown>` |  | A handle to associate the menu with a trigger. If specified, allows external triggers to control the menu's open state. |
| `children` | `PayloadChildRenderFunction<unknown> \| React.ReactNode` |  | The content of the menu. This can be a regular React node or a render function that receives the `payload` of the active trigger. |

### Menu.Trigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `style` | `React.CSSProperties \| ((state: MenuTriggerState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `children` | `React.ReactNode` |  |  |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `handle` | `MenuHandle<unknown>` |  | A handle to associate the trigger with a menu. |
| `payload` | `unknown` |  | A payload to pass to the menu when it is opened. |
| `delay` | `number` | `100` | How long to wait before the menu may be opened on hover. Specified in milliseconds. Requires the `openOnHover` prop. |
| `closeDelay` | `number` | `0` | How long to wait before closing the menu that was opened on hover. Specified in milliseconds. Requires the `openOnHover` prop. |
| `openOnHover` | `boolean` |  | Whether the menu should also open when the trigger is hovered. |
| `className` | `string` |  | Classes for the part. Styled parts merge them last with `cn()`. |
| `render` | `ComponentRenderFn<HTMLProps, MenuTriggerState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Replaces the styled trigger with your element, such as a library `Button`. The trigger then adds only behaviour and ARIA, no classes or content. |
| `size` | `"default" \| "small"` |  | Defaults to `'default'`. |
| `iconOnly` | `boolean` |  | Renders the square trigger with no label or chevron. A `leadingIcon` replaces the children, and without either the trigger shows the dots icon. Give it an `aria-label`. |
| `leadingIcon` | `MenuSlot<MenuTriggerState>` |  | An icon before the label: an element, a render function or text. |
| `chevron` | `boolean` |  | Shows the chevron after the label. Defaults to `true`. It points up while the menu is open. |

### Menu.Portal

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: MenuPortalState) => string \| undefined)` |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| `render` | `ComponentRenderFn<HTMLProps, MenuPortalState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuPortalState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `keepMounted` | `boolean` | `false` | Whether to keep the portal mounted in the DOM while the popup is hidden. |
| `container` | `HTMLElement \| React.RefObject<HTMLElement \| ShadowRoot \| null> \| ShadowRoot \| null` |  | A parent element to render the portal element into. |

### Menu.Positioner

`side` defaults to `bottom`, `align` to `end` and `sideOffset` to 8. Inside a `Menu.SubmenuRoot`, `side` defaults to `inline-end`, `align` to `start` and `alignOffset` to -4.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: MenuPositionerState) => string \| undefined)` |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| `render` | `ComponentRenderFn<HTMLProps, MenuPositionerState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuPositionerState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `anchor` | `Element \| React.RefObject<Element \| null> \| VirtualElement \| (() => Element \| VirtualElement \| null) \| null` |  | An element to position the popup against. By default, the popup will be positioned against the trigger. |
| `positionMethod` | `"absolute" \| "fixed"` | `'absolute'` | Determines which CSS `position` property to use. |
| `sideOffset` | `number \| OffsetFunction` | `0` | Distance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. |
| `alignOffset` | `number \| OffsetFunction` | `0` | Additional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. |
| `collisionBoundary` | `Boundary` | `'clipping-ancestors'` | An element or a rectangle that delimits the area that the popup is confined to. |
| `collisionPadding` | `Padding` | `5` | Additional space to maintain from the edge of the collision boundary. |
| `sticky` | `boolean` | `false` | Whether to maintain the popup in the viewport after the anchor element was scrolled out of view. |
| `arrowPadding` | `number` | `5` | Minimum distance to maintain between the arrow and the edges of the popup. Use it to prevent the arrow element from hanging out of the rounded corners of a popup. |
| `disableAnchorTracking` | `boolean` | `false` | Whether to disable the popup from tracking any layout shift of its positioning anchor. |
| `collisionAvoidance` | `CollisionAvoidance` |  | Determines how to handle collisions when positioning the popup. `side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`): - `'flip'`: keep the requested side when it fits; otherwise try the opposite side (`top` and `bottom`, or `left` and `right`). - `'shift'`: never change side; keep the requested side and move the popup within the clipping boundary so it stays visible. - `'none'`: do not correct side-axis overflow. `align` controls overflow on the alignment axis (`start`/`center`/`end`): - `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows. - `'shift'`: keep side and requested alignment, then nudge the popup along the alignment axis to fit. - `'none'`: do not correct alignment-axis overflow. `fallbackAxisSide` controls fallback behavior on the perpendicular axis when the preferred axis cannot fit: - `'start'`: allow perpendicular fallback and try the logical start side first (`top` before `bottom`, or `left` before `right` in LTR). - `'end'`: allow perpendicular fallback and try the logical end side first (`bottom` before `top`, or `right` before `left` in LTR). - `'none'`: do not fallback to the perpendicular axis. When `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`. If `align` is omitted, it defaults to `'flip'`. |
| `align` | `"center" \| "end" \| "start"` | `'center'` | How to align the popup relative to the specified side. Submenus and menubars default to `'start'`. |
| `side` | `"bottom" \| "inline-end" \| "inline-start" \| "left" \| "right" \| "top"` | `'bottom'` | Which side of the anchor element to align the popup against. May automatically change to avoid collisions. Submenus and vertical menubars default to `'inline-end'`. |

### Menu.Popup

| Prop | Type | Description |
| --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, MenuPopupState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuPopupState) => React.CSSProperties \| undefined)` | Style applied to the element, or a function that returns a style object based on the component's state. |
| `children` | `React.ReactNode` |  |
| `id` | `string` |  |
| `finalFocus` | `boolean \| React.RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| HTMLElement \| null \| void)` | Determines the element to focus when the menu is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing. |
| `className` | `string` | Classes for the part. Styled parts merge them last with `cn()`. |
| `maxHeight` | `string \| number` | The tallest the item list grows before it scrolls, in px or any CSS length. Unset, the panel hugs its items up to the space the viewport has. |

### Menu.Item

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, MenuItemState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuItemState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `onClick` | `((event: BaseUIEvent<React.MouseEvent<HTMLDivElement, MouseEvent>>) => void)` |  | The click handler for the menu item. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `label` | `string` |  | Overrides the text used for keyboard text navigation and filtering. Falls back to the rendered text when not provided. |
| `id` | `string` |  |  |
| `closeOnClick` | `boolean` | `true` | Whether to close the menu when the item is clicked. |
| `children` | `React.ReactNode` |  | The label. It wraps and the row grows. |
| `description` | `React.ReactNode` |  | A second line under the label. |
| `leadingIcon` | `MenuSurfaceSlot<MenuSurfaceItemState>` |  | A 16px icon before the label. |
| `endContent` | `MenuSurfaceSlot<MenuSurfaceItemState>` |  | Content at the end of the row, such as a shortcut. Truncates at 60px. |
| `destructive` | `boolean` |  | Draws the item in the error colours. Use it only for delete actions. |
| `className` | `string` |  | Classes merged last with `cn()`. |

### Menu.LinkItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, MenuLinkItemState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuLinkItemState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `label` | `string` |  | Overrides the text used for keyboard text navigation and filtering. Falls back to the rendered text when not provided. |
| `id` | `string` |  |  |
| `closeOnClick` | `boolean` | `false` | Whether to close the menu when the item is clicked. |
| `children` | `React.ReactNode` |  | The label. It wraps and the row grows. |
| `description` | `React.ReactNode` |  | A second line under the label. |
| `leadingIcon` | `MenuSurfaceSlot<MenuSurfaceItemState>` |  | A 16px icon before the label. |
| `endContent` | `MenuSurfaceSlot<MenuSurfaceItemState>` |  | Content at the end of the row, such as a shortcut. Truncates at 60px. |
| `destructive` | `boolean` |  | Draws the item in the error colours. Use it only for delete actions. |
| `className` | `string` |  | Classes merged last with `cn()`. |

### Menu.CheckboxItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, MenuCheckboxItemState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuCheckboxItemState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `checked` | `boolean` |  | Whether the checkbox item is currently ticked. To render an uncontrolled checkbox item, use the `defaultChecked` prop instead. |
| `defaultChecked` | `boolean` | `false` | Whether the checkbox item is initially ticked. To render a controlled checkbox item, use the `checked` prop instead. |
| `onCheckedChange` | `((checked: boolean, eventDetails: MenuCheckboxItem.ChangeEventDetails) => void)` |  | Event handler called when the checkbox item is ticked or unticked. |
| `onClick` | `((event: BaseUIEvent<React.MouseEvent<HTMLDivElement, MouseEvent>>) => void)` |  | The click handler for the menu item. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `label` | `string` |  | Overrides the text used for keyboard text navigation and filtering. Falls back to the rendered text when not provided. |
| `id` | `string` |  |  |
| `closeOnClick` | `boolean` | `false` | Whether to close the menu when the item is clicked. |
| `children` | `React.ReactNode` |  | The label. It wraps and the row grows. |
| `description` | `React.ReactNode` |  | A second line under the label. |
| `leadingIcon` | `MenuSurfaceSlot<MenuSurfaceItemState>` |  | A 16px icon before the label. |
| `endContent` | `MenuSurfaceSlot<MenuSurfaceItemState>` |  | Content at the end of the row, such as a shortcut. Truncates at 60px. |
| `destructive` | `boolean` |  | Draws the item in the error colours. Use it only for delete actions. |
| `className` | `string` |  | Classes merged last with `cn()`. |

### Menu.RadioGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: MenuRadioGroupState) => string \| undefined)` |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| `render` | `ComponentRenderFn<HTMLProps, MenuRadioGroupState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuRadioGroupState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `children` | `React.ReactNode` |  | The content of the component. |
| `value` | `any` |  | The controlled value of the radio item that should be currently selected. To render an uncontrolled radio group, use the `defaultValue` prop instead. |
| `defaultValue` | `any` |  | The uncontrolled value of the radio item that should be initially selected. To render a controlled radio group, use the `value` prop instead. |
| `onValueChange` | `((value: any, eventDetails: MenuRadioGroup.ChangeEventDetails) => void)` |  | Function called when the selected value changes. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |

### Menu.RadioItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, MenuRadioItemState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuRadioItemState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `value` (required) | `any` |  | Value of the radio item. This is the value that will be set in the MenuRadioGroup when the item is selected. |
| `onClick` | `((event: BaseUIEvent<React.MouseEvent<HTMLDivElement, MouseEvent>>) => void)` |  | The click handler for the menu item. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `label` | `string` |  | Overrides the text used for keyboard text navigation and filtering. Falls back to the rendered text when not provided. |
| `id` | `string` |  |  |
| `closeOnClick` | `boolean` | `false` | Whether to close the menu when the item is clicked. |
| `children` | `React.ReactNode` |  | The label. It wraps and the row grows. |
| `description` | `React.ReactNode` |  | A second line under the label. |
| `leadingIcon` | `MenuSurfaceSlot<MenuSurfaceItemState>` |  | A 16px icon before the label. |
| `endContent` | `MenuSurfaceSlot<MenuSurfaceItemState>` |  | Content at the end of the row, such as a shortcut. Truncates at 60px. |
| `destructive` | `boolean` |  | Draws the item in the error colours. Use it only for delete actions. |
| `className` | `string` |  | Classes merged last with `cn()`. |
| `indicator` | `"end" \| "start"` |  | Where the tick of the checked item sits. Defaults to `'end'`. |

### Menu.Group

| Prop | Type | Description |
| --- | --- | --- |
| `className` | `string \| ((state: MenuGroupState) => string \| undefined)` | CSS class applied to the element, or a function that returns a class based on the component's state. |
| `render` | `ComponentRenderFn<HTMLProps, MenuGroupState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuGroupState) => React.CSSProperties \| undefined)` | Style applied to the element, or a function that returns a style object based on the component's state. |
| `children` | `React.ReactNode` | The content of the component. |

### Menu.GroupLabel

| Prop | Type | Description |
| --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, MenuGroupLabelState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuGroupLabelState) => React.CSSProperties \| undefined)` | Style applied to the element, or a function that returns a style object based on the component's state. |
| `className` | `string` | Classes for the part. Styled parts merge them last with `cn()`. |

### Menu.Separator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, SeparatorState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: SeparatorState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `orientation` | `"horizontal" \| "vertical"` | `'horizontal'` | The orientation of the separator. |
| `className` | `string` |  | Classes for the part. Styled parts merge them last with `cn()`. |

### Menu.SubmenuRoot

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `onOpenChange` | `((open: boolean, eventDetails: MenuSubmenuRoot.ChangeEventDetails) => void)` |  | Event handler called when the menu is opened or closed. |
| `closeParentOnEsc` | `boolean` | `false` | When in a submenu, determines whether pressing the Escape key closes the entire menu, or only the current child menu. |
| `children` | `React.ReactNode` |  | The content of the submenu. |
| `defaultOpen` | `boolean` | `false` | Whether the menu is initially open. To render a controlled menu, use the `open` prop instead. |
| `loopFocus` | `boolean` | `true` | Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. |
| `highlightItemOnHover` | `boolean` | `true` | Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state. |
| `onOpenChangeComplete` | `((open: boolean) => void)` |  | Event handler called after any animations complete when the menu is opened or closed. |
| `onItemHighlighted` | `((highlightedItem: HTMLElement \| undefined, eventDetails: MenuRoot.HighlightEventDetails) => void)` |  | Callback fired when an item is highlighted or unhighlighted. Receives the highlighted item element (or `undefined` if no item is highlighted) and details containing the reason for the change, the event, and the item's text label. The `reason` can be: - `'keyboard'`: the highlight changed due to keyboard navigation. - `'pointer'`: the highlight changed due to pointer hovering. The event may be a `MouseEvent` rather than a `PointerEvent`. - `'none'`: the highlight changed for another reason, such as automatic highlighting while filtering, the item list changing, or the popup opening or closing. |
| `open` | `boolean` |  | Whether the menu is currently open. |
| `orientation` | `"horizontal" \| "vertical"` | `'vertical'` | The visual orientation of the menu. Controls whether roving focus uses up/down or left/right arrow keys. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `actionsRef` | `React.RefObject<MenuRootActions \| null>` |  | A ref to imperative actions. - `unmount`: Ends the closing phase of the menu after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the menu completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the menu imperatively when called. |

### Menu.SubmenuTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, MenuSubmenuTriggerState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: MenuSubmenuTriggerState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `onClick` | `((event: BaseUIEvent<React.MouseEvent<HTMLDivElement, MouseEvent>>) => void)` |  |  |
| `label` | `string` |  | Overrides the text used for keyboard text navigation and filtering. Falls back to the rendered text when not provided. |
| `id` | `string` |  |  |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `delay` | `number` | `100` | How long to wait before the menu may be opened on hover. Specified in milliseconds. Requires the `openOnHover` prop. |
| `closeDelay` | `number` | `0` | How long to wait before closing the menu that was opened on hover. Specified in milliseconds. Requires the `openOnHover` prop. |
| `openOnHover` | `boolean` | `true` | Whether the menu should also open when the trigger is hovered. |
| `children` | `React.ReactNode` |  | The label. It wraps and the row grows. |
| `description` | `React.ReactNode` |  | A second line under the label. |
| `leadingIcon` | `MenuSurfaceSlot<MenuSurfaceItemState>` |  | A 16px icon before the label. |
| `endContent` | `MenuSurfaceSlot<MenuSurfaceItemState>` |  | Content at the end of the row, such as a shortcut. Truncates at 60px. |
| `destructive` | `boolean` |  | Draws the item in the error colours. Use it only for delete actions. |
| `className` | `string` |  | Classes merged last with `cn()`. |

### Menu.Search

`placeholder` defaults to "Search..." and `aria-label` to "Search". The ref and `className` reach the input.

| Prop | Type | Description |
| --- | --- | --- |
| `placeholder` | `string` | Defaults to `'Search...'`. |
| `render` | `ComponentRenderFn<HTMLProps, FilterDropdownInputState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: FilterDropdownInputState) => React.CSSProperties \| undefined)` | Style applied to the element, or a function that returns a style object based on the component's state. |
| `className` | `string` | Classes for the part. Styled parts merge them last with `cn()`. |

### Menu.FilterProvider

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `React.ReactNode` |  | The `<Menu.Root>` or `<Menu.SubmenuRoot>` to make filterable. |
| `filter` | `((text: string, query: string) => boolean) \| null` |  | Filter function used to match items against the query. Receives each item's `label` (or its rendered text) and the trimmed query, and keeps the item when it returns `true`. By default, items match when they contain the query, ignoring case, accents, and punctuation. Pass `null` when rendering filtered items yourself. |
| `autoHighlight` | `"always" \| boolean` | `false` | Whether filtering highlights the first matching item automatically. - `true`: highlight it while the query is not empty. - `'always'`: highlight it even when the query is empty. Opening the menu from the keyboard highlights the first item either way, and the arrow keys can move the highlight to another item. With either value, the arrow keys wrap within the list rather than returning to the input, and with `'always'` a pointer highlight stays when the pointer leaves. |
| `locale` | `Intl.LocalesArgument` |  | The locale the default `filter` uses for string comparison. Defaults to the user's runtime locale. |
| `defaultValue` | `string` |  | The uncontrolled filter query when the menu is initially rendered. To render a controlled query, use the `value` prop instead. |
| `value` | `string` |  | The filter query. Use when controlled. When the popup closes, `onValueChange` is called with an empty query. The controlled value changes only when the consumer updates this prop. |
| `onValueChange` | `((value: string, eventDetails: MenuFilterProvider.ChangeEventDetails) => void)` |  | Event handler called when the filter query changes. |