@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
478 lines (427 loc) • 12.6 kB
Markdown
---
title: 'Menu'
description: 'Menu is a composable dropdown menu component for actions and navigation, with keyboard navigation, nested menus, and full accessibility support.'
version: 11.0.0
generatedAt: 2026-04-21T13:57:51.571Z
checksum: 1fc99517592ef537de08c221f7492a20a5f0da34d4c6422f3025968fc4f94199
---
# Menu
## Import
```tsx
import { Menu } from '@dnb/eufemia'
```
## Description
Menu provides an accessible dropdown menu for actions and navigation with a composable, tree-shakeable API.
Use `Menu.Root` as the wrapper, `Menu.Button` for the trigger, `Menu.List` for the list container, `Menu.Action` for individual items, and `Menu.Divider` for visual separators.
`Menu.Button` supports all [Button](/uilib/components/button/properties) props (e.g. `text`, `icon`, `variant`, `size`, `disabled`), so you can customise the trigger the same way you would with a regular Button.
Nested menus are supported by nesting another `Menu.Root` inside `Menu.List` — use a `Menu.Action` as the direct child of the nested `Menu.Root` to serve as the sub-menu trigger.
For inline expandable groups, use `Menu.Accordion` instead of a nested `Menu.Root`. It reveals child items with a height animation inside the current menu, rather than opening a separate popover.
## Relevant links
- Source code: https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-eufemia/src/components/menu
- Docs code: https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-design-system-portal/src/docs/uilib/components/menu
## Accessibility
- The menu uses ARIA `role="menu"` and `role="menuitem"` semantics.
- The trigger receives `aria-haspopup="menu"` and `aria-expanded` attributes automatically.
- Keyboard navigation follows the [WAI-ARIA Menu Pattern](https://www.w3.org/WAI/ARIA/apd/patterns/menu/):
- **Arrow Up/Down**: Move focus between items (wraps around).
- **Home/End**: Jump to first/last item.
- **Enter/Space**: Activate the focused item.
- **Escape**: Close the menu.
- **Tab**: Close the menu and move focus naturally.
- **Arrow Right**: Open a sub-menu (when the item has one).
- **Arrow Left**: Close a sub-menu and return to the parent.
- Type-ahead: pressing a letter key jumps to the first matching item.
- Focus is moved to the menu container when it opens. Arrow keys then move focus to individual items. Focus returns to the trigger when the menu closes.
- Disabled items receive `aria-disabled` and are skipped during keyboard navigation.
- Dividers use `role="separator"`.
## Demos
### Basic Menu
```tsx
render(
<Menu.Root>
<Menu.Button />
<Menu.List>
<Menu.Action text="Action" onClick={() => null} />
<Menu.Action text="Link" href="https://www.dnb.no/" />
</Menu.List>
</Menu.Root>
)
```
### Accordion
```tsx
render(
<Menu.Root>
<Menu.Button text="File" icon="chevron_down" />
<Menu.List>
<Menu.Action
icon={file_add}
text="New"
onClick={() => console.log('new')}
/>
<Menu.Action
icon={folder}
text="Open"
onClick={() => console.log('open')}
/>
<Menu.Divider />
<Menu.Accordion icon={folder} text="Export as">
<Menu.Action
icon={file_pdf}
text="PDF"
onClick={() => console.log('export pdf')}
/>
<Menu.Action
icon={file_png}
text="PNG"
onClick={() => console.log('export png')}
/>
</Menu.Accordion>
<Menu.Divider />
<Menu.Action
icon={save}
text="Save"
onClick={() => console.log('save')}
/>
</Menu.List>
</Menu.Root>
)
```
### Nested Menu
```tsx
render(
<Menu.Root arrowPosition="left">
<Menu.Button text="File" icon="chevron_down" />
<Menu.List>
<Menu.Action
icon={file_add}
text="New"
onClick={() => console.log('new')}
/>
<Menu.Action
icon={folder}
text="Open"
onClick={() => console.log('open')}
/>
<Menu.Divider />
<Menu.Root placement="right" arrowPosition="top">
<Menu.Action icon={folder} text="Export as" />
<Menu.List>
<Menu.Action
icon={file_pdf}
text="PDF"
onClick={() => console.log('export pdf')}
/>
<Menu.Action
icon={file_png}
text="PNG"
onClick={() => console.log('export png')}
/>
<Menu.Action
icon={file}
text="SVG"
onClick={() => console.log('export svg')}
/>
</Menu.List>
</Menu.Root>
<Menu.Divider />
<Menu.Action
icon="close"
text="Close"
onClick={() => console.log('close')}
/>
</Menu.List>
</Menu.Root>
)
```
### With Links
```tsx
render(
<Menu.Root>
<Menu.Button text="Navigate" icon="chevron_down" variant="tertiary" />
<Menu.List>
<Menu.Action icon={home} text="Home" href="/" />
<Menu.Action icon={layout_card} text="Dashboard" href="/dashboard" />
<Menu.Action
icon={launch}
text="External"
href="https://example.com"
target="_blank"
rel="noopener noreferrer"
/>
</Menu.List>
</Menu.Root>
)
```
### Max Visible List Items
```tsx
render(
<Menu.Root>
<Menu.Button text="Long list" icon="chevron_down" />
<Menu.List maxVisibleListItems={4}>
<Menu.Action text="Item 1" />
<Menu.Action text="Item 2" />
<Menu.Action text="Item 3" />
<Menu.Action text="Item 4" />
<Menu.Action text="Item 5" />
<Menu.Action text="Item 6" />
<Menu.Action text="Item 7" />
<Menu.Action text="Item 8" />
</Menu.List>
</Menu.Root>
)
```
### With Headers
```tsx
render(
<Menu.Root>
<Menu.Button text="Edit" icon="chevron_down" />
<Menu.List>
<Menu.Header text="Clipboard" />
<Menu.Action
icon={scissors}
text="Cut"
onClick={() => console.log('cut')}
/>
<Menu.Action
icon={copy}
text="Copy"
onClick={() => console.log('copy')}
/>
<Menu.Action icon={edit} text="Paste" disabled />
<Menu.Divider />
<Menu.Header text="Selection" />
<Menu.Action icon="check" text="Select All" />
</Menu.List>
</Menu.Root>
)
```
## Menu.Root
```json
{
"props": {
"open": {
"doc": "Controlled open state. Use together with `onOpenChange`.",
"type": "boolean",
"status": "optional"
},
"arrowPosition": {
"doc": "Position of the popover arrow relative to the popover. `top` and `bottom` positions are only applicable when `placement` is `left` or `right`, and vice versa.",
"type": [
"\"left\"",
"\"right\"",
"\"center\"",
"\"top\"",
"\"bottom\""
],
"defaultValue": "\"center\"",
"status": "optional"
},
"placement": {
"doc": "Preferred placement of the menu relative to the trigger.",
"type": ["\"top\"", "\"right\"", "\"bottom\"", "\"left\""],
"defaultValue": "\"bottom\"",
"status": "optional"
},
"autoAlignMode": {
"doc": "Control when the menu automatically flips its placement to fit within the viewport. `\"initial\"`: flip only on open. `\"scroll\"`: also flip during scroll. `\"never\"`: always use specified placement.",
"type": ["\"initial\"", "\"scroll\"", "\"never\""],
"defaultValue": "\"initial\"",
"status": "optional"
},
"skipPortal": {
"doc": "Render inline instead of inside a portal.",
"type": "boolean",
"defaultValue": "false",
"status": "optional"
},
"noAnimation": {
"doc": "Disable the open/close animation.",
"type": "boolean",
"defaultValue": "false",
"status": "optional"
}
}
}
```
## Menu.Button
```json
{
"props": {
"icon": {
"doc": "Icon displayed on the trigger button.",
"type": "IconIcon",
"defaultValue": "\"more\"",
"status": "optional"
},
"variant": {
"doc": "Button variant.",
"type": ["\"primary\"", "\"secondary\"", "\"tertiary\""],
"defaultValue": "\"secondary\"",
"status": "optional"
},
"text": {
"doc": "Visible text label for the trigger button.",
"type": "string",
"status": "optional"
},
"[Button props]": {
"doc": "All [Button](/uilib/components/button/properties) props are supported.",
"type": "Various",
"status": "optional"
}
}
}
```
## Menu.List
```json
{
"props": {
"children": {
"doc": "Menu items. Use `Menu.Action` and `Menu.Divider` as direct children.",
"type": "React.ReactNode",
"status": "required"
},
"maxVisibleListItems": {
"doc": "Sets the maximum visible list items before the content scrolls. The component measures the rendered height of the first visible items. An explicit `style.maxHeight` overrides this.",
"type": "number",
"status": "optional"
}
}
}
```
## Menu.Action
```json
{
"props": {
"text": {
"doc": "Action label text.",
"type": "React.ReactNode",
"status": "optional"
},
"icon": {
"doc": "Icon displayed before the text.",
"type": "IconIcon",
"status": "optional"
},
"href": {
"doc": "When provided, the action renders as a link.",
"type": "string",
"status": "optional"
},
"to": {
"doc": "Use this property when using a router Link component as the `element`. The `to` value is passed to the router element for client-side navigation.",
"type": "string",
"status": "optional"
},
"element": {
"doc": "Define what HTML or React element should be used for the link (e.g. `element={Link}` for a router Link component). Defaults to a semantic `a` element.",
"type": "React.Element",
"status": "optional"
},
"target": {
"doc": "Link target attribute (e.g. `_blank`).",
"type": "string",
"status": "optional"
},
"rel": {
"doc": "Link rel attribute (e.g. `noopener noreferrer`).",
"type": "string",
"status": "optional"
},
"disabled": {
"doc": "Disables the action. Sets `aria-disabled` and prevents click/keyboard activation.",
"type": "boolean",
"defaultValue": "false",
"status": "optional"
},
"children": {
"doc": "Custom content rendered inside the action item.",
"type": "React.ReactNode",
"status": "optional"
}
}
}
```
## Menu.Accordion
```json
{
"props": {
"text": {
"doc": "Accordion trigger label text.",
"type": "React.ReactNode",
"status": "optional"
},
"icon": {
"doc": "Icon displayed before the text.",
"type": "IconIcon",
"status": "optional"
},
"disabled": {
"doc": "Disables the accordion trigger. Sets `aria-disabled` and prevents toggling.",
"type": "boolean",
"defaultValue": "false",
"status": "optional"
},
"children": {
"doc": "Menu items rendered inside the accordion when open. Use `Menu.Action` and `Menu.Divider` as children.",
"type": "React.ReactNode",
"status": "required"
}
}
}
```
## Menu.Header
```json
{
"props": {
"text": {
"doc": "Header text displayed in the menu.",
"type": "React.ReactNode",
"status": "optional"
},
"children": {
"doc": "Alternative to `text`. Content rendered inside the header.",
"type": "React.ReactNode",
"status": "optional"
}
}
}
```
## Menu.Divider
No properties.
## Menu.Root Events
```json
{
"props": {
"onOpenChange": {
"doc": "Called whenever the open state changes. Receives the new open state as a boolean.",
"type": "(open: boolean) => void",
"status": "optional"
}
}
}
```
## Menu.Action Events
```json
{
"props": {
"onClick": {
"doc": "Called when the action is clicked or activated via keyboard (Enter/Space). The menu closes automatically after the handler is invoked unless used as a trigger for a nested `Menu.Root`.",
"type": "(event: React.MouseEvent<HTMLLIElement>) => void",
"status": "optional"
}
}
}
```
## Menu.Accordion Events
```json
{
"props": {
"onOpenChange": {
"doc": "Called whenever the accordion open state changes. Receives the new open state as a boolean.",
"type": "(open: boolean) => void",
"status": "optional"
}
}
}
```