# Menu

**An accessible dropdown and context menu that displays a list of actions or options.**

**Reference:** [View Source](https://github.com/jramke/fluid-primitives/tree/main/Resources/Private/Primitives/Menu) · [Zag.js Docs](https://zagjs.com/components/react/menu)

**Menu.html**

```html
<ui:menu.root>
    <ui:menu.trigger>Open Menu</ui:menu.trigger>
    <ui:menu.content>
        <ui:menu.item value="new-file">New File</ui:menu.item>
        <ui:menu.item value="new-window">New Window</ui:menu.item>
        <ui:menu.separator />
        <ui:menu.item value="settings">Settings</ui:menu.item>
    </ui:menu.content>
</ui:menu.root>

```

**Menu.ts**

```ts
import { mountAll } from 'fluid-primitives';
import { Menu } from 'fluid-primitives/menu';

mountAll('ui:menu', ({ props }) => {
    const menu = new Menu(props);
    menu.init();
    return menu;
});

```

## Features

- Support for items, labels, and groups of items
- Support for checkbox and radio menu items
- Support for nested/submenu patterns
- Support for context menus (triggered by right-click) and multiple independent triggers sharing one menu
- Focus is fully managed using the `aria-activedescendant` pattern
- Typeahead to allow focusing items by typing text
- Full keyboard navigation support, including arrow keys, home/end, and submenu navigation

## Installation

```bash
typo3 ui:add menu
```

Or copy the files manually from GitHub (https://github.com/jramke/fluid-primitives.com/tree/main/packages/docs/Resources/Private/Registry/Menu) into your project.

Read more about installing [Components and Primitives](/docs/core-concepts/primitives.md).

## Examples

### Grouping

Organize items into logical groups with their own labels.

**Menu.html**

```html
<ui:menu.root>
    <ui:menu.trigger>File</ui:menu.trigger>
    <ui:menu.content>
        <ui:menu.itemGroup value="file-group">
            <ui:menu.itemGroupLabel>File</ui:menu.itemGroupLabel>
            <ui:menu.item value="new">New</ui:menu.item>
            <ui:menu.item value="open">Open</ui:menu.item>
            <ui:menu.item value="save">Save</ui:menu.item>
        </ui:menu.itemGroup>
        <ui:menu.separator />
        <ui:menu.itemGroup value="edit-group">
            <ui:menu.itemGroupLabel>Edit</ui:menu.itemGroupLabel>
            <ui:menu.item value="undo">Undo</ui:menu.item>
            <ui:menu.item value="redo">Redo</ui:menu.item>
        </ui:menu.itemGroup>
    </ui:menu.content>
</ui:menu.root>

```

### With Links

Pass `asChild="{true}"` on `menu.item` to render the item's attributes and behavior directly onto a single child element, like a plain `<a href>`, so it behaves like real navigation, including keyboard activation.

**Menu.html**

```html
<ui:menu.root>
    <ui:menu.trigger>Navigate</ui:menu.trigger>
    <ui:menu.content>
        <ui:menu.item asChild="{true}" value="home">
            <a href="/">Home</a>
        </ui:menu.item>
        <ui:menu.item asChild="{true}" value="docs">
            <a href="/docs/introduction">Documentation</a>
        </ui:menu.item>
        <ui:menu.item asChild="{true}" value="github">
            <a href="https://github.com/jramke/fluid-primitives" target="_blank" rel="noreferrer">GitHub</a>
        </ui:menu.item>
    </ui:menu.content>
</ui:menu.root>

```

### With Checkboxes

Use `menu.checkboxItem` for items that toggle independently of each other.

**Menu.html**

```html
<ui:menu.root closeOnSelect="{false}">
    <ui:menu.trigger>View</ui:menu.trigger>
    <ui:menu.content>
        <ui:menu.checkboxItem value="toolbar">Show Toolbar</ui:menu.checkboxItem>
        <ui:menu.checkboxItem value="sidebar" checked="{true}">Show Sidebar</ui:menu.checkboxItem>
        <ui:menu.checkboxItem value="statusbar">Show Status Bar</ui:menu.checkboxItem>
    </ui:menu.content>
</ui:menu.root>

```

### With Radios

Use `menu.radioItem` with a shared `name` for mutually exclusive options.

**Menu.html**

```html
<ui:menu.root closeOnSelect="{false}">
    <ui:menu.trigger>Sort By</ui:menu.trigger>
    <ui:menu.content>
        <ui:menu.radioItem name="sort" value="name" checked="{true}">Name</ui:menu.radioItem>
        <ui:menu.radioItem name="sort" value="date">Date Modified</ui:menu.radioItem>
        <ui:menu.radioItem name="sort" value="size">Size</ui:menu.radioItem>
    </ui:menu.content>
</ui:menu.root>
```

### Nested Menu

A submenu is just another `menu.root`, linked to its parent by two explicit ids: give the submenu's `menu.root` a `rootId` and point its `parentId` back at the parent menu's own `rootId`, then render a `menu.triggerItem` inside the _parent's_ own content with a `childId` matching the submenu's `rootId`. The submenu itself doesn't need to live anywhere near the parent's markup - it's linked entirely by id.

**Menu.html**

```html
<ui:menu.root rootId="file-menu">
    <ui:menu.trigger>File</ui:menu.trigger>
    <ui:menu.content>
        <ui:menu.item value="new-tab">New Tab</ui:menu.item>
        <ui:menu.item value="new-window">New Window</ui:menu.item>
        <ui:menu.separator />
        <ui:menu.triggerItem childId="share-menu">Share</ui:menu.triggerItem>
        <ui:menu.triggerItem childId="export-menu">Export</ui:menu.triggerItem>
        <ui:menu.separator />
        <ui:menu.item value="print">Print…</ui:menu.item>
    </ui:menu.content>
</ui:menu.root>

<ui:menu.root rootId="share-menu" parentId="file-menu" positioning="{ placement: 'right-start' }">
    <ui:menu.content>
        <ui:menu.item value="email">Email</ui:menu.item>
        <ui:menu.item value="messages">Messages</ui:menu.item>
        <ui:menu.item value="airdrop">AirDrop</ui:menu.item>
    </ui:menu.content>
</ui:menu.root>

<ui:menu.root rootId="export-menu" parentId="file-menu" positioning="{ placement: 'right-start' }">
    <ui:menu.content>
        <ui:menu.item value="pdf">PDF</ui:menu.item>
        <ui:menu.item value="png">PNG</ui:menu.item>
        <ui:menu.item value="svg">SVG</ui:menu.item>
    </ui:menu.content>
</ui:menu.root>
```

### Context Menu

Use `menu.contextTrigger` to open the menu on right-click instead of (or in addition to) a regular trigger.

**Menu.html**

```html
<ui:menu.root>
    <ui:menu.contextTrigger>
        Right-click this area
    </ui:menu.contextTrigger>
    <ui:menu.content>
        <ui:menu.item value="cut">Cut</ui:menu.item>
        <ui:menu.item value="copy">Copy</ui:menu.item>
        <ui:menu.item value="paste">Paste</ui:menu.item>
    </ui:menu.content>
</ui:menu.root>

```

### Multiple Triggers

Give several `menu.trigger` elements different `value`s to share a single menu instance between them.

**Menu.html**

```html
<ui:menu.root>
    <div class="flex gap-2">
        <ui:menu.trigger value="a">Trigger A</ui:menu.trigger>
        <ui:menu.trigger value="b">Trigger B</ui:menu.trigger>
    </div>
    <ui:menu.content>
        <ui:menu.item value="edit">Edit</ui:menu.item>
        <ui:menu.item value="duplicate">Duplicate</ui:menu.item>
        <ui:menu.item value="delete">Delete</ui:menu.item>
    </ui:menu.content>
</ui:menu.root>

```

### Inside a Dialog

By default `menu.content` portals to the end of the document body, same as Select. Pass `portalled="{false}"` when nesting a menu inside another portalled/focus-trapped element, like a dialog, so it stays within that element's DOM subtree instead.

**Menu.html**

```html
<ui:dialog.root>
    <ui:dialog.trigger asChild="{true}">
        <ui:button>Open Dialog</ui:button>
    </ui:dialog.trigger>
    <ui:dialog.content>
        <ui:dialog.header>
            <ui:dialog.title>Project Settings</ui:dialog.title>
            <ui:dialog.description>
                The menu below sets <code>portalled="{false}"</code> on <code>ui:menu.content</code> so it
                stays inside this dialog's DOM subtree instead of portalling to the document body.
            </ui:dialog.description>
        </ui:dialog.header>
        <ui:menu.root>
            <ui:menu.trigger class="self-start">Options</ui:menu.trigger>
            <ui:menu.content portalled="{false}">
                <ui:menu.item value="rename">Rename</ui:menu.item>
                <ui:menu.item value="duplicate">Duplicate</ui:menu.item>
                <ui:menu.item value="archive">Archive</ui:menu.item>
            </ui:menu.content>
        </ui:menu.root>
        <ui:dialog.footer>
            <ui:dialog.close asChild="{true}">
                <ui:button variant="secondary">Close</ui:button>
            </ui:dialog.close>
        </ui:dialog.footer>
    </ui:dialog.content>
</ui:dialog.root>
```

## API Reference

### menu.root

Provides shared menu state to the rest of the parts. Renders no element of its own.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `closeOnSelect` | `boolean` | No | `true` | Whether to close the menu when an option is selected. |
| `composite` | `boolean` | No | `true` | Whether the menu is composed with other composite widgets like a combobox or tabs. |
| `typeahead` | `boolean` | No | `true` | Whether pressing printable characters should trigger typeahead navigation. |
| `loopFocus` | `boolean` | No | `false` | Whether to loop the keyboard navigation. |
| `positioning` | `array` | No | `{     "placement": "bottom-start",     "gutter": 8 }` | The options used to dynamically position the menu. |
| `defaultOpen` | `boolean` | No | `false` | The initial open state of the menu when rendered. Use when you don't need to control the open state of the menu. |
| `defaultHighlightedValue` | `string` | No | `-` | The initial highlighted value of the menu item when rendered. |
| `defaultTriggerValue` | `string` | No | `-` | The initial trigger value when rendered, for a menu with multiple triggers. |
| `ariaLabel` | `string` | No | `-` | The accessibility label for the menu. Forwarded to the machine as `aria-label`. |
| `parentId` | `string` | No | `-` | The `rootId` of the parent menu this menu is a submenu of. Must match a `menu.triggerItem`'s `childId` on that parent, pointing back at this menu's own `rootId` - see the Nested Menu docs example. |
| `rootId` | `string` | No | `-` | The root ID of the component, used for hydration and identification. |
| `ids` | `array` | No | `[]` | The IDs of of the component parts for composition. |
| `controlled` | `boolean` | No | `false` | If true, the component is meant to be initialized manually inside another component |

### menu.trigger

Opens and closes the menu. Renders a `<button>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `string` | No | `-` | The value that identifies this specific trigger, for a menu with multiple triggers. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |

#### Rendered data attributes

| Attribute | Description |
| --- | --- |
| `data-scope` | menu |
| `data-part` | trigger |
| `data-placement` | The placement of the trigger |
| `data-side` | The side of the trigger that the trigger is positioned on |
| `data-value` | The value of the item |
| `data-current` | Present when current |
| `data-controls` |  |
| `data-state` | "open" \| "closed" |

### menu.contextTrigger

Opens the menu at the cursor position on right-click. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `string` | No | `-` | The value that identifies this specific context trigger, for a menu with multiple context triggers. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |

#### Rendered data attributes

| Attribute | Description |
| --- | --- |
| `data-scope` | menu |
| `data-part` | context-trigger |
| `data-value` | The value of the item |
| `data-current` | Present when current |
| `data-state` | "open" \| "closed" |

### menu.content

Contains the menu items. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |

#### Rendered data attributes

| Attribute | Description |
| --- | --- |
| `data-scope` | menu |
| `data-part` | content |
| `data-state` | "open" \| "closed" |
| `data-nested` | menu |
| `data-has-nested` | menu |
| `data-placement` | The placement of the content |
| `data-side` | The side of the trigger that the content is positioned on |

### menu.item

A selectable menu action. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `string` | Yes | `-` | The unique value of the menu item. |
| `disabled` | `boolean` | No | `-` | Whether the menu item is disabled. |
| `valueText` | `string` | No | `-` | The textual value of the item, used for typeahead navigation. Falls back to the item's text content when omitted. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |

#### Rendered data attributes

| Attribute | Description |
| --- | --- |
| `data-scope` | menu |
| `data-part` | item |
| `data-disabled` | Present when disabled |
| `data-highlighted` | Present when highlighted |
| `data-value` | The value of the item |
| `data-valuetext` | The human-readable value |

### menu.checkboxItem

A menu item that toggles independently of other items. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `string` | Yes | `-` | The unique value of the checkbox item. |
| `checked` | `boolean` | No | `false` | Whether the checkbox item is checked when rendered. |
| `disabled` | `boolean` | No | `-` | Whether the checkbox item is disabled. |
| `valueText` | `string` | No | `-` | The textual value of the item, used for typeahead navigation. Falls back to the item's text content when omitted. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |

### menu.radioItem

A menu item that's mutually exclusive with other radio items sharing the same `name`. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `string` | Yes | `-` | The unique value of the radio item. |
| `checked` | `boolean` | No | `false` | Whether the radio item is checked when rendered. |
| `disabled` | `boolean` | No | `-` | Whether the radio item is disabled. |
| `valueText` | `string` | No | `-` | The textual value of the item, used for typeahead navigation. Falls back to the item's text content when omitted. |
| `name` | `string` | No | `-` | Groups radio items so selecting one unchecks the others sharing the same `name`. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |

### menu.itemGroup

Groups related items together. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `string` | Yes | `-` | The value identifier of the item group. Picked up automatically by a `menu.itemGroupLabel` nested inside it. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |

### menu.itemGroupLabel

Labels an `itemGroup`. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |

### menu.separator

A visual divider between items. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |

### menu.triggerItem

Opens a nested submenu from within another menu's content. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `childId` | `string` | Yes | `-` | The `rootId` of the submenu (its `menu.root`'s `rootId`) that this item opens. That submenu's own `parentId` must point back at this menu's `rootId`. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |

### Machine JavaScript API

| Name | Type | Description |
| --- | --- | --- |
| `open` | `boolean` | Whether the menu is open |
| `setOpen` | `(open: boolean) => void` | Function to open or close the menu |
| `triggerValue` | `string \| null` | The trigger value |
| `setTriggerValue` | `(value: string \| null) => void` | Function to set the trigger value |
| `highlightedValue` | `string \| null` | The id of the currently highlighted menuitem |
| `setHighlightedValue` | `(value: string) => void` | Function to set the highlighted menuitem |
| `setParent` | `(parent: MenuService) => void` | Function to register a parent menu. This is used for submenus |
| `setChild` | `(child: MenuService) => void` | Function to register a child menu. This is used for submenus |
| `reposition` | `(options?: Partial<PositioningOptions>) => void` | Function to reposition the popover |
| `getOptionItemState` | `(props: OptionItemProps) => OptionItemState` | Returns the state of the option item |
| `getItemState` | `(props: ItemProps) => ItemState` | Returns the state of the menu item |
| `addItemListener` | `(props: ItemListenerProps) => VoidFunction \| undefined` | Setup the custom event listener for item selection event |

### Accessibility

| Key | Description |
| --- | --- |
| `Space`  | Activates/Selects the highlighted item |
| `Enter`  | Activates/Selects the highlighted item |
| `ArrowDown`  | Highlights the next item in the menu |
| `ArrowUp`  | Highlights the previous item in the menu |
| `ArrowRight` `ArrowLeft`  | <span>When focus is on trigger, opens or closes the submenu depending on reading direction.</span> |
| `Esc`  | Closes the menu and moves focus to the trigger |

## Anatomy

```html
<primitives:menu.root>
    <primitives:menu.trigger>
        <primitives:menu.indicator />
    </primitives:menu.trigger>
    <primitives:menu.contextTrigger />
    <primitives:menu.positioner>
        <primitives:menu.arrow />
        <primitives:menu.content>
            <primitives:menu.item>
                <primitives:menu.itemText />
            </primitives:menu.item>
            <primitives:menu.checkboxItem>
                <primitives:menu.itemIndicator />
                <primitives:menu.itemText />
            </primitives:menu.checkboxItem>
            <primitives:menu.radioItem>
                <primitives:menu.itemIndicator />
                <primitives:menu.itemText />
            </primitives:menu.radioItem>
            <primitives:menu.separator />
            <primitives:menu.itemGroup>
                <primitives:menu.itemGroupLabel />
            </primitives:menu.itemGroup>

            <f:comment><!-- Opens the submenu below, matched by rootId/childId --></f:comment>
            <primitives:menu.triggerItem childId="submenu" />
        </primitives:menu.content>
    </primitives:menu.positioner>
</primitives:menu.root>

<f:comment><!-- A submenu: linked to its parent by id, not by nesting --></f:comment>
<primitives:menu.root rootId="submenu" parentId="parent-rootId">
    <primitives:menu.positioner>
        <primitives:menu.content>
            <primitives:menu.item />
        </primitives:menu.content>
    </primitives:menu.positioner>
</primitives:menu.root>
```
