# Menu (/docs/menu)





## API Reference [#api-reference]

<BaseUIReference href="https://base-ui.com/react/components/menu" />

## Basic [#basic]

`MenuContent` renders the portal and positioner internally, so the public composition only needs the trigger, content, optional arrow, and menu items.

<Preview cssProperties="menuPlaygroundCssProperties">
  <MenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            MenuContent,
            MenuItem,
            MenuSeparator,
          } from "moduix";

          export function MenuDemo() {
            return (
              <Menu>
                <MenuTrigger render={<Button />}>
                  Song
                  <MenuTriggerIcon />
                </MenuTrigger>
                <MenuContent>
                  <MenuItem closeOnClick>Add to Library</MenuItem>
                  <MenuItem closeOnClick>Add to Playlist</MenuItem>
                  <MenuSeparator />
                  <MenuItem closeOnClick>Play Next</MenuItem>
                  <MenuItem closeOnClick>Play Last</MenuItem>
                  <MenuSeparator />
                  <MenuItem closeOnClick disabled>
                    Share
                  </MenuItem>
                </MenuContent>
              </Menu>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSSProperties>
    {(context) => <MenuCssPropertiesPanel {...context} />}
  </Preview.CSSProperties>

  <Preview.CSSPlayground>
    {(context) => <MenuCssPlaygroundPanel {...context} />}
  </Preview.CSSPlayground>
</Preview>

## Anatomy [#anatomy]

Menu is composed from a trigger and a popup content layer. Keep interactive items and nested branches
inside `MenuContent` so roving focus, keyboard navigation, and close behavior stay managed by the root.

```text
Menu
├─ MenuTrigger
│  └─ MenuTriggerIcon
└─ MenuContent
   ├─ MenuArrow
   ├─ MenuItem
   ├─ MenuSeparator
   └─ MenuSubmenu
      ├─ MenuSubmenuTrigger
      │  └─ MenuSubmenuTriggerIcon
      └─ MenuSubmenuContent
         └─ MenuItem
```

```tsx
<Menu>
  <MenuTrigger>
    Song
    <MenuTriggerIcon />
  </MenuTrigger>
  <MenuContent>
    <MenuItem closeOnClick>Add to Library</MenuItem>
    <MenuSubmenu>
      <MenuSubmenuTrigger>
        Add to Playlist
        <MenuSubmenuTriggerIcon />
      </MenuSubmenuTrigger>
      <MenuSubmenuContent>
        <MenuItem closeOnClick>Get Up!</MenuItem>
      </MenuSubmenuContent>
    </MenuSubmenu>
  </MenuContent>
</Menu>
```

| Part                    | Role                                                                                                     |
| ----------------------- | -------------------------------------------------------------------------------------------------------- |
| `Menu`                  | Root state machine. Handles open state, focus management, and close interactions.                        |
| `MenuTrigger`           | Control that opens and closes the menu. Accepts trigger props like `openOnHover`, `delay`, and `handle`. |
| `MenuTriggerIcon`       | Optional trigger indicator. Renders the default icon or your custom icon as children.                    |
| `MenuContent`           | Popup container for items. Accepts positioning props and renders service slots internally.               |
| `MenuArrow`             | Optional visual pointer from popup to trigger.                                                           |
| `MenuItem`              | Action row. Supports disabled state and `closeOnClick` behavior.                                         |
| `MenuLinkItem`          | Item variant for navigation links while preserving menu behavior.                                        |
| `MenuSeparator`         | Visual divider between related item groups.                                                              |
| `MenuSubmenu*`          | Nested branch for secondary actions (`Trigger`, `TriggerIcon`, and `Content`).                           |
| `MenuGroup*` / controls | Optional structured controls: labels, radio groups/items, checkbox items, and indicator slots.           |

`MenuContent` renders the Base UI portal, positioner, optional backdrop, optional viewport,
and popup internally. In most cases, keep those service slots hidden and style visible parts
such as trigger, items, separators, and arrow. Use `classNames` for service-slot classes and
`slotProps` only when you need to pass non-class props to `portal`, `backdrop`, `positioner`,
`arrow`, or `viewport`.

## Composition [#composition]

Use `Menu` for root state and behavior props such as `open`, `defaultOpen`,
`onOpenChange`, and `handle`. Add `withViewport` only when you need Base UI content transitions for
menus shared by multiple detached triggers.

`className` styles the visible popup. `classNames` styles the internal service slots hidden from
the default composition. `MenuContent` forwards Base UI popup props and accepts positioner props
such as `side`, `sideOffset`, `align`, `alignOffset`, `arrowPadding`, `anchor`,
`collisionAvoidance`, `collisionBoundary`, `collisionPadding`, `sticky`, `positionMethod`,
and `disableAnchorTracking` directly. Use `container`, `withBackdrop`, and `withViewport` for
common service-slot behavior, and use `slotProps` for the rare cases that need lower-level
Base UI portal, backdrop, positioner, arrow, or viewport props. Arrow is enabled by default;
set `withArrow={false}` to disable it:

```tsx
<MenuContent
  className={styles.popup}
  classNames={{
    portal: styles.portal,
    backdrop: styles.backdrop,
    positioner: styles.positioner,
    arrow: styles.arrow,
    viewport: styles.viewport,
  }}
  slotProps={{
    positioner: {
      collisionPadding: 8,
    },
  }}
  withBackdrop
  withViewport
/>
```

## Examples [#examples]

### Without Arrow [#without-arrow]

Set `withArrow={false}` when the popup should appear without a pointer.

<Preview>
  <WithoutArrowMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            MenuContent,
            MenuItem,
            MenuSeparator,
          } from "moduix";

          export function WithoutArrowMenu() {
            return (
              <Menu>
                <MenuTrigger render={<Button />}>
                  Song
                  <MenuTriggerIcon />
                </MenuTrigger>
                <MenuContent withArrow={false}>
                  <MenuItem closeOnClick>Add to Library</MenuItem>
                  <MenuItem closeOnClick>Add to Playlist</MenuItem>
                  <MenuSeparator />
                  <MenuItem closeOnClick>Play Next</MenuItem>
                </MenuContent>
              </Menu>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Groups And Controls [#groups-and-controls]

Use groups, radio items, and checkbox items when the menu changes application state.

<Preview>
  <GroupsAndControlsMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            MenuContent,
            MenuGroup,
            MenuGroupLabel,
            MenuRadioGroup,
            MenuRadioItem,
            MenuRadioItemIndicator,
            MenuItemText,
            MenuSeparator,
            MenuCheckboxItem,
            MenuCheckboxItemIndicator,
          } from "moduix";
          import { useState } from "react";

          export function GroupsAndControlsMenu() {
            const [sortBy, setSortBy] = useState("date");
            const [showMinimap, setShowMinimap] = useState(true);

            return (
              <Menu>
                <MenuTrigger render={<Button />}>
                  View
                  <MenuTriggerIcon />
                </MenuTrigger>
                <MenuContent>
                  <MenuGroup>
                    <MenuGroupLabel>Sort</MenuGroupLabel>
                    <MenuRadioGroup value={sortBy} onValueChange={setSortBy}>
                      <MenuRadioItem value="date">
                        <MenuRadioItemIndicator />
                        <MenuItemText>Date</MenuItemText>
                      </MenuRadioItem>
                      <MenuRadioItem value="name">
                        <MenuRadioItemIndicator />
                        <MenuItemText>Name</MenuItemText>
                      </MenuRadioItem>
                    </MenuRadioGroup>
                  </MenuGroup>
                  <MenuSeparator />
                  <MenuGroup>
                    <MenuGroupLabel>Workspace</MenuGroupLabel>
                    <MenuCheckboxItem checked={showMinimap} onCheckedChange={setShowMinimap}>
                      <MenuCheckboxItemIndicator />
                      <MenuItemText>Minimap</MenuItemText>
                    </MenuCheckboxItem>
                  </MenuGroup>
                </MenuContent>
              </Menu>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Shortcuts [#shortcuts]

Use `MenuItemShortcut` for shortcut text that aligns to the end of each item.

<Preview>
  <ShortcutsMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            MenuContent,
            MenuItem,
            MenuItemShortcut,
            MenuSeparator,
          } from "moduix";

          export function ShortcutsMenu() {
            return (
              <Menu>
                <MenuTrigger render={<Button />}>
                  Edit
                  <MenuTriggerIcon />
                </MenuTrigger>
                <MenuContent>
                  <MenuItem closeOnClick>
                    Copy
                    <MenuItemShortcut>Ctrl+C</MenuItemShortcut>
                  </MenuItem>
                  <MenuItem closeOnClick>
                    Paste
                    <MenuItemShortcut>Ctrl+V</MenuItemShortcut>
                  </MenuItem>
                  <MenuSeparator />
                  <MenuItem closeOnClick>
                    Rename
                    <MenuItemShortcut>F2</MenuItemShortcut>
                  </MenuItem>
                </MenuContent>
              </Menu>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Indicator Right With Icon [#indicator-right-with-icon]

Use `indicator="end"` to align checkbox and radio indicators to the end. Text and icon slots accept custom content and `className`.

<Preview>
  <IndicatorRightMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            MenuContent,
            MenuCheckboxItem,
            MenuItemText,
            MenuItemTextContent,
            MenuItemTextIcon,
            InfoIcon,
            MenuItemTextLabel,
            MenuCheckboxItemIndicator,
          } from "moduix";
          import { useState } from "react";

          export function IndicatorRightMenu() {
            const [showMinimap, setShowMinimap] = useState(true);

            return (
              <Menu>
                <MenuTrigger render={<Button />}>
                  View
                  <MenuTriggerIcon />
                </MenuTrigger>
                <MenuContent>
                  <MenuCheckboxItem checked={showMinimap} onCheckedChange={setShowMinimap} indicator="end">
                    <MenuItemText>
                      <MenuItemTextContent>
                        <MenuItemTextIcon>
                          <InfoIcon />
                        </MenuItemTextIcon>
                        <MenuItemTextLabel>Minimap</MenuItemTextLabel>
                      </MenuItemTextContent>
                    </MenuItemText>
                    <MenuCheckboxItemIndicator />
                  </MenuCheckboxItem>
                </MenuContent>
              </Menu>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Nested [#nested]

Use `MenuSubmenu` with `MenuSubmenuTrigger` and `MenuSubmenuContent` for nested actions.

<Preview>
  <NestedMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            MenuContent,
            MenuItem,
            MenuSubmenu,
            MenuSubmenuTrigger,
            MenuSubmenuTriggerIcon,
            MenuSubmenuContent,
            MenuSeparator,
          } from "moduix";

          export function NestedMenu() {
            return (
              <Menu>
                <MenuTrigger render={<Button />}>
                  Song
                  <MenuTriggerIcon />
                </MenuTrigger>
                <MenuContent>
                  <MenuItem closeOnClick>Add to Library</MenuItem>
                  <MenuSubmenu>
                    <MenuSubmenuTrigger>
                      Add to Playlist
                      <MenuSubmenuTriggerIcon />
                    </MenuSubmenuTrigger>
                    <MenuSubmenuContent>
                      <MenuItem closeOnClick>Get Up!</MenuItem>
                      <MenuItem closeOnClick>Inside Out</MenuItem>
                      <MenuItem closeOnClick>Night Beats</MenuItem>
                      <MenuSeparator />
                      <MenuItem closeOnClick>New Playlist...</MenuItem>
                    </MenuSubmenuContent>
                  </MenuSubmenu>
                </MenuContent>
              </Menu>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Open On Hover [#open-on-hover]

Use `openOnHover` when the trigger should behave like a hover menu while keeping keyboard support.

<Preview>
  <OpenOnHoverMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            MenuContent,
            MenuItem,
            MenuSeparator,
          } from "moduix";

          export function OpenOnHoverMenu() {
            return (
              <Menu>
                <MenuTrigger render={<Button />} openOnHover delay={120}>
                  Add to playlist
                  <MenuTriggerIcon />
                </MenuTrigger>
                <MenuContent>
                  <MenuItem closeOnClick>Get Up!</MenuItem>
                  <MenuItem closeOnClick>Inside Out</MenuItem>
                  <MenuItem closeOnClick>Night Beats</MenuItem>
                  <MenuSeparator />
                  <MenuItem closeOnClick>New Playlist...</MenuItem>
                </MenuContent>
              </Menu>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Positioned With Backdrop [#positioned-with-backdrop]

Use `side`, `align`, and offset props on `MenuContent` to position the popup. Add `withBackdrop` when the menu should render an internal backdrop; style internal service slots through `classNames`.

<Preview>
  <PositionedWithBackdropMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            MenuContent,
            MenuItem,
            MenuSeparator,
          } from "moduix";

          export function PositionedWithBackdropMenu() {
            return (
              <Menu>
                <MenuTrigger render={<Button />}>
                  Export
                  <MenuTriggerIcon />
                </MenuTrigger>
                <MenuContent side="right" align="start" sideOffset={12} withBackdrop>
                  <MenuItem closeOnClick>Export PNG</MenuItem>
                  <MenuItem closeOnClick>Export PDF</MenuItem>
                  <MenuSeparator />
                  <MenuItem closeOnClick>Copy share link</MenuItem>
                </MenuContent>
              </Menu>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Open Alert Dialog [#open-alert-dialog]

Open a dialog from a menu item by closing the menu on click and updating dialog state in the item handler.

<Preview>
  <OpenAlertDialogMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            MenuContent,
            MenuItem,
            MenuSeparator,
            AlertDialog,
            AlertDialogAction,
            AlertDialogCancel,
            AlertDialogContent,
            AlertDialogDescription,
            AlertDialogFooter,
            AlertDialogHeader,
            AlertDialogTitle,
          } from "moduix";
          import { useState, Fragment } from "react";

          export function OpenAlertDialogMenu() {
            const [dialogOpen, setDialogOpen] = useState(false);

            return (
              <Fragment>
                <Menu>
                  <MenuTrigger render={<Button />}>
                    Project
                    <MenuTriggerIcon />
                  </MenuTrigger>
                  <MenuContent>
                    <MenuItem closeOnClick>Rename</MenuItem>
                    <MenuItem closeOnClick>Duplicate</MenuItem>
                    <MenuSeparator />
                    <MenuItem
                      closeOnClick
                      onClick={() => {
                        setDialogOpen(true);
                      }}
                    >
                      Delete...
                    </MenuItem>
                  </MenuContent>
                </Menu>

                <AlertDialog open={dialogOpen} onOpenChange={setDialogOpen}>
                  <AlertDialogContent>
                    <AlertDialogHeader>
                      <AlertDialogTitle>Delete project?</AlertDialogTitle>
                      <AlertDialogDescription>
                        This action cannot be undone and will permanently remove all environments.
                      </AlertDialogDescription>
                    </AlertDialogHeader>
                    <AlertDialogFooter>
                      <AlertDialogCancel>Cancel</AlertDialogCancel>
                      <AlertDialogAction>Delete</AlertDialogAction>
                    </AlertDialogFooter>
                  </AlertDialogContent>
                </AlertDialog>
              </Fragment>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Link Items [#link-items]

Use `MenuLinkItem` for navigation items that should render as links.

<Preview>
  <LinkItemsMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            MenuContent,
            MenuLinkItem,
            MenuSeparator,
            MenuItem,
          } from "moduix";

          export function LinkItemsMenu() {
            return (
              <Menu>
                <MenuTrigger render={<Button />}>
                  Navigate
                  <MenuTriggerIcon />
                </MenuTrigger>
                <MenuContent>
                  <MenuLinkItem href="#projects">Projects</MenuLinkItem>
                  <MenuLinkItem href="#teams">Teams</MenuLinkItem>
                  <MenuLinkItem href="#billing">Billing</MenuLinkItem>
                  <MenuSeparator />
                  <MenuItem closeOnClick>Copy Link</MenuItem>
                </MenuContent>
              </Menu>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Detached Trigger [#detached-trigger]

Use `createMenuHandle` when the trigger and root need to be rendered in different places.

<Preview>
  <DetachedTriggerMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            createMenuHandle,
            MenuTrigger,
            MenuTriggerIcon,
            Menu,
            MenuContent,
            MenuItem,
            MenuSeparator,
          } from "moduix";
          import { useMemo, Fragment } from "react";

          export function DetachedTriggerMenu() {
            const menuHandle = useMemo(() => createMenuHandle(), []);

            return (
              <Fragment>
                <div className={styles.detachedTrigger}>
                  <MenuTrigger render={<Button />} handle={menuHandle}>
                    Actions
                    <MenuTriggerIcon />
                  </MenuTrigger>
                </div>

                <Menu handle={menuHandle}>
                  <MenuContent>
                    <MenuItem closeOnClick>Edit</MenuItem>
                    <MenuItem closeOnClick>Share</MenuItem>
                    <MenuSeparator />
                    <MenuItem closeOnClick>Archive</MenuItem>
                  </MenuContent>
                </Menu>
              </Fragment>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .detachedTrigger {
            display: flex;
            justify-content: center;
            margin-bottom: var(--spacing-4);
          }
        `}
  </Preview.CSS>
</Preview>

### Custom Styles [#custom-styles]

Use `className` for visible slots and `classNames` for internal service slots. Icon slots accept children from your application or an external icon library.

<Preview>
  <CustomStylesMenuExample />

  <Preview.Code>
    {`
          import {
            Button,
            Menu,
            MenuTrigger,
            MenuTriggerIcon,
            ChevronDownIcon,
            MenuContent,
            MenuItem,
            MenuItemTextContent,
            MenuItemTextIcon,
            MapIcon,
            MenuItemTextLabel,
            MenuSubmenu,
            MenuSubmenuTrigger,
            InfoIcon,
            MenuSubmenuTriggerIcon,
            MenuSubmenuContent,
          } from "moduix";

          export function CustomStylesMenu() {
            return (
              <Menu>
                <MenuTrigger render={<Button />} className={styles.customTrigger}>
                  Places
                  <MenuTriggerIcon className={styles.customTriggerIcon}>
                    <ChevronDownIcon />
                  </MenuTriggerIcon>
                </MenuTrigger>
                <MenuContent
                  className={styles.customPopup}
                  classNames={{
                    portal: styles.customPortal,
                    backdrop: styles.customBackdrop,
                    positioner: styles.customPositioner,
                  }}
                  withBackdrop
                >
                  <MenuItem closeOnClick className={styles.customItem}>
                    <MenuItemTextContent>
                      <MenuItemTextIcon>
                        <MapIcon />
                      </MenuItemTextIcon>
                      <MenuItemTextLabel>Open map</MenuItemTextLabel>
                    </MenuItemTextContent>
                  </MenuItem>
                  <MenuSubmenu>
                    <MenuSubmenuTrigger className={styles.customItem}>
                      <MenuItemTextContent>
                        <MenuItemTextIcon>
                          <InfoIcon />
                        </MenuItemTextIcon>
                        <MenuItemTextLabel>More</MenuItemTextLabel>
                      </MenuItemTextContent>
                      <MenuSubmenuTriggerIcon>
                        <ChevronDownIcon />
                      </MenuSubmenuTriggerIcon>
                    </MenuSubmenuTrigger>
                    <MenuSubmenuContent>
                      <MenuItem closeOnClick className={styles.customItem}>
                        Nearby
                      </MenuItem>
                      <MenuItem closeOnClick className={styles.customItem}>
                        Routes
                      </MenuItem>
                    </MenuSubmenuContent>
                  </MenuSubmenu>
                </MenuContent>
              </Menu>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .customTrigger {
            border-color: var(--color-primary);
            background:
              linear-gradient(var(--color-background), var(--color-background)) padding-box,
              linear-gradient(135deg, var(--color-primary), var(--color-ring)) border-box;
            color: var(--color-primary);
          }

          .customTriggerIcon {
            --menu-trigger-icon-size: 0.875rem;
          }

          .customPortal {
            position: relative;
          }

          .customBackdrop {
            background-color: rgb(0 0 0 / 0.08);
            backdrop-filter: blur(0.125rem);
          }

          .customPositioner {
            z-index: var(--z-popup);
          }

          .customPopup {
            --menu-popup-min-width: 13rem;
            --menu-popup-bg: var(--color-background);
            --menu-popup-border-color: var(--color-primary);
            --menu-popup-shadow: var(--shadow-lg);
            --menu-highlight-bg: var(--color-primary);
            --menu-highlight-color: var(--color-primary-foreground);
            --menu-separator-color: var(--color-primary);
          }

          .customItem {
            --menu-item-padding-x-start: var(--spacing-3);
            --menu-item-padding-x-end: var(--spacing-3);
            --menu-item-text-icon-color: var(--color-primary);
          }
        `}
  </Preview.CSS>
</Preview>
