# Menu (/docs/menu)





`Menu` is the kit's **dropdown of actions**, a styled layer over [Ark UI's Menu](https://ark-ui.com/). Ark owns the open state, positioning, roving focus, type-ahead, and the `role="menu"` / `menuitem` ARIA wiring; the kit adds the portaled, animated surface and Mantine-style item props. It ships **two APIs from one import** — compose the parts for full control, or pass an `items` array for the common case.

```tsx
import { Menu } from '@42-central/ui-react/menu';

<Menu.Root>
  <Menu.Trigger asChild>
    <Button>Open</Button>
  </Menu.Trigger>
  <Menu.Content>
    <Menu.Item startSection={<User />} onClick={openProfile}>Profile</Menu.Item>
    <Menu.Divider />
    <Menu.Item color="red" startSection={<Trash />} onClick={remove}>Delete</Menu.Item>
  </Menu.Content>
</Menu.Root>
```

## Playground [#playground]

<story.Playground />

## Basic usage [#basic-usage]

`Menu.Item` takes `startSection` / `endSection` (RTL-aware, like Mantine's left/right sections) and a `color` for destructive actions. `Menu.Label` heads a section and `Menu.Divider` separates them. The dropdown is portaled (escapes `overflow: hidden` ancestors) and animated.

<Preview align="center" code="&#x22;<Menu.Root>\n  <Menu.Trigger asChild>\n    <Button endSlot={<ChevronDown />}>Account</Button>\n  </Menu.Trigger>\n  <Menu.Content>\n    <Menu.Label>Application</Menu.Label>\n    <Menu.Item startSection={<User />}>Profile</Menu.Item>\n    <Menu.Item startSection={<Settings />} endSection={<span>⌘S</span>}>Settings</Menu.Item>\n    <Menu.Item startSection={<Users />}>Team</Menu.Item>\n    <Menu.Divider />\n    <Menu.Item startSection={<LogOut />}>Log out</Menu.Item>\n    <Menu.Item color=\&#x22;red\&#x22; startSection={<Trash2 />}>Delete account</Menu.Item>\n  </Menu.Content>\n</Menu.Root>&#x22;">
  <MenuExample />
</Preview>

## Data-driven [#data-driven]

For simple action menus, describe the whole thing as a `data` array — built on the same parts, the way [`Autocomplete`](/docs/autocomplete) is built on [`Combobox`](/docs/combobox). Each item's `onClick` fires on activation (click *and* keyboard); a bare string is shorthand for `{ label, value }`, `{ type: 'divider' }` is a separator, and `{ group, items }` is a section. A group takes an optional decorative `prefix` (the code-themed `// ` leader, off by default; hidden from screen readers and the clipboard). The component is generic over `const data`, so an item's explicit `value` narrows the optional top-level `onSelect(value)`.

<Preview align="center" code="&#x22;<Menu\n  data={[\n    { label: 'Profile', value: 'profile', startSection: <User /> },\n    { label: 'Settings', value: 'settings', startSection: <Settings /> },\n    { type: 'divider' },\n    { group: 'Danger zone', prefix: '// ', items: [\n      { label: 'Delete', value: 'delete', color: 'red', startSection: <Trash2 /> },\n    ]},\n  ]}\n  onSelect={(value) => setLast(value)} // value: 'profile' | 'settings' | 'delete'\n>\n  <Button endSlot={<ChevronDown />}>Actions</Button>\n</Menu>&#x22;">
  <MenuDataExample />
</Preview>

## Link items [#link-items]

An item that navigates should be a real link, so middle-click, "open in new tab", the URL on hover and the link role all work. Pass your router's link component as the item's only child with `asChild`: the item becomes that link, and `startSection`, `endSection`, `color` and the highlight still apply. Keyboard selection (Enter) clicks the link, so client-side routing keeps working. Every item part composes the same way: `Menu.CheckboxItem`, `Menu.RadioItem`, `Menu.SubTrigger` and `Menu.ItemGroupLabel` included.

<Preview align="center" code="&#x22;import { Link as RouterLink } from 'your-router';\n\n<Menu.Content>\n  <Menu.Item asChild startSection={<BookOpen />}>\n    <RouterLink href=\&#x22;/docs/breadcrumb\&#x22;>Breadcrumb docs</RouterLink>\n  </Menu.Item>\n  <Menu.Item asChild startSection={<BookOpen />}>\n    <RouterLink href=\&#x22;/docs/alert\&#x22;>Alert docs</RouterLink>\n  </Menu.Item>\n  <Menu.Divider />\n  <Menu.Item asChild startSection={<Link />} endSection={<ExternalLink />}>\n    <a href=\&#x22;https://ark-ui.com/docs/components/menu\&#x22; target=\&#x22;_blank\&#x22; rel=\&#x22;noreferrer\&#x22;>\n      Ark UI Menu\n    </a>\n  </Menu.Item>\n</Menu.Content>&#x22;">
  <MenuLinksExample />
</Preview>

## Submenus [#submenus]

Nest a `Menu.Sub` and open it from a `Menu.SubTrigger`; its dropdown is a `Menu.SubContent` (same surface as `Menu.Content`). In the data-driven form, an item with both a `label` and its own `items` becomes a submenu automatically.

<Preview align="center" code="&#x22;<Menu.Content>\n  <Menu.Item startSection={<Copy />}>Copy</Menu.Item>\n  <Menu.Item startSection={<Scissors />}>Cut</Menu.Item>\n  <Menu.Divider />\n  <Menu.Sub>\n    <Menu.SubTrigger startSection={<Share2 />}>Share</Menu.SubTrigger>\n    <Menu.SubContent>\n      <Menu.Item startSection={<Link />}>Copy link</Menu.Item>\n      <Menu.Item startSection={<Mail />}>Email</Menu.Item>\n    </Menu.SubContent>\n  </Menu.Sub>\n</Menu.Content>&#x22;">
  <MenuSubmenuExample />
</Preview>

## Checkbox & radio [#checkbox--radio]

`Menu.CheckboxItem` and `Menu.RadioGroup` / `Menu.RadioItem` carry selection state. Set `closeOnSelect={false}` on `Menu.Root` to keep the dropdown open while toggling several options.

<Preview align="center" code="&#x22;<Menu.Root closeOnSelect={false}>\n  <Menu.Trigger asChild><Button endSlot={<ChevronDown />}>View</Button></Menu.Trigger>\n  <Menu.Content>\n    <Menu.Label>Editor</Menu.Label>\n    <Menu.CheckboxItem value=\&#x22;wrap\&#x22; checked={wrap} onCheckedChange={setWrap}>Word wrap</Menu.CheckboxItem>\n    <Menu.CheckboxItem value=\&#x22;minimap\&#x22; checked={minimap} onCheckedChange={setMinimap}>Minimap</Menu.CheckboxItem>\n    <Menu.Divider />\n    <Menu.Label>Theme</Menu.Label>\n    <Menu.RadioGroup value={theme} onChange={setTheme}>\n      <Menu.RadioItem value=\&#x22;light\&#x22;>Light</Menu.RadioItem>\n      <Menu.RadioItem value=\&#x22;dark\&#x22;>Dark</Menu.RadioItem>\n      <Menu.RadioItem value=\&#x22;system\&#x22;>System</Menu.RadioItem>\n    </Menu.RadioGroup>\n  </Menu.Content>\n</Menu.Root>&#x22;">
  <MenuSelectionExample />
</Preview>

## Context menu [#context-menu]

Swap `Menu.Trigger` for `Menu.ContextTrigger` to open on right-click (and long-press on touch).

<Preview align="center" code="&#x22;<Menu.Root>\n  <Menu.ContextTrigger asChild>\n    <div>Right-click here</div>\n  </Menu.ContextTrigger>\n  <Menu.Content>\n    <Menu.Item startSection={<Copy />}>Copy</Menu.Item>\n    <Menu.Item startSection={<Scissors />}>Cut</Menu.Item>\n    <Menu.Divider />\n    <Menu.Item color=\&#x22;red\&#x22; startSection={<Trash2 />}>Delete</Menu.Item>\n  </Menu.Content>\n</Menu.Root>&#x22;">
  <MenuContextExample />
</Preview>

## Anatomy [#anatomy]

* **`Menu.Root`** — wraps Ark's `Menu.Root`; owns open state + positioning.
* **`Menu.Trigger`** — the button that opens the menu; use `asChild&#x60; to wrap your own element. &#x2A;*`Menu.ContextTrigger`** opens on right-click instead.
* **`Menu.Content`** — the portaled, animated dropdown.
* **`Menu.Item`** — an action row, with `startSection` / `endSection` / `color`.
* **`Menu.Label`*&#x2A; / &#x2A;*`Menu.Divider`*&#x2A; — a section heading and a separator. For ARIA-grouped sections use &#x2A;*`Menu.ItemGroup`*&#x2A; + &#x2A;*`Menu.ItemGroupLabel`**. Both headings accept an optional decorative `prefix` (e.g. `"// "`, off by default).
* **`Menu.CheckboxItem`*&#x2A;, &#x2A;*`Menu.RadioGroup`*&#x2A; / &#x2A;*`Menu.RadioItem`** — stateful selection rows.
* **`Menu.Sub`*&#x2A; / &#x2A;*`Menu.SubTrigger`*&#x2A; / &#x2A;*`Menu.SubContent`** — a nested submenu.

## API [#api]

`Menu.Root` props (beyond Ark's `Menu.Root`):

<TypeTable
  type="{
  position: { type: 'Placement | &#x22;start&#x22; | &#x22;end&#x22;', default: '&#x22;bottom-start&#x22;', description: 'Dropdown placement. Prefer the logical, RTL-aware &#x22;start&#x22; / &#x22;end&#x22; sides, or any Ark placement (&#x22;bottom-start&#x22;, &#x22;top&#x22;, …).' },
  offset: { type: 'number', description: 'Gap in px between the trigger and the dropdown.' },
  withinPortal: { type: 'boolean', default: 'true', description: 'Render the dropdown in a Portal so it escapes clipping ancestors.' },
  container: { type: 'HTMLElement | null', description: 'Portal target. Defaults to document.body.' },
  open: { type: 'boolean', description: 'Controlled open state.' },
  defaultOpen: { type: 'boolean', description: 'Uncontrolled initial open state.' },
  onOpenChange: { type: '(open: boolean) => void', description: 'Fires when the menu opens or closes (flattened from Ark).' },
  onClose: { type: '() => void', description: 'Fires on close (any source) — Escape, outside interaction, item selection, or a controlled onOpenChange.' },
  closeOnSelect: { type: 'boolean', default: 'true', description: 'Close the menu when an item is selected.' },
  loopFocus: { type: 'boolean', default: 'false', description: 'Wrap keyboard navigation from last item to first.' },
}"
/>

Every rendered part — `Menu.Content`, `Menu.Item`, `Menu.CheckboxItem`, `Menu.RadioItem`,
`Menu.SubTrigger`, `Menu.Label`, `Menu.ItemGroupLabel`, `Menu.Divider` — also takes its own
`testId` prop, rendered as `data-testid` on that part. See the [Testing](/docs/testing) guide.

`Menu.Item` props (beyond Ark's `Menu.Item`):

<TypeTable
  type="{
  startSection: { type: 'ReactNode', description: 'Content before the label, usually an icon (inline-start).' },
  endSection: { type: 'ReactNode', description: 'Content after the label, pushed to the inline-end.' },
  color: { type: 'Color', description: 'Palette for the row — colors the label + highlight tint (e.g. &#x22;red&#x22;).' },
  value: { type: 'string', description: 'Unique value for type-ahead / selection. Auto-generated when omitted.' },
  disabled: { type: 'boolean', description: 'Disable the item.' },
  onClick: { type: '() => void', description: 'Fires when the item is selected — on click and keyboard activation (Enter / Space).' },
}"
/>

`<Menu data>` (data-driven) props:

<TypeTable
  type="{
  data: { type: 'MenuItemsData', description: 'Entries: string | action | { type: &#x22;divider&#x22; } | { group, items, prefix? } | submenu ({ label, items }).' },
  children: { type: 'ReactNode', description: 'The trigger element (rendered via asChild).' },
  onSelect: { type: '(value: MenuValue<Data>) => void', description: 'Fires with the selected item\'s value, typed to the data\'s value union.' },
}"
/>

Each entry also accepts an optional `testId`, defaulting to its resolved `value` (explicit or
auto-generated) — see the [Testing](/docs/testing) guide.
