@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
994 lines (880 loc) • 31.8 kB
Markdown
# Menu
Import: `import { Menu } from '@neo4j-ndl/react'`
## Props
### Menu
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `anchorRef` | `RefObject<HTMLElement \| null>` | | | Anchor element ref for positioning |
| `as` | `ElementType<any, keyof IntrinsicElements>` | | | An override of the default HTML tag of the root of the component. Can also be another React component. |
| `children` | `ReactNode` | | | Content displayed inside the menu |
| `description` | `ReactNode` | | | Optional description for nested trigger rendering |
| `icon` | `ReactNode` | | | Optional icon for nested trigger rendering |
| `isDisabled` | `boolean` | | | Whether the menu trigger is disabled |
| `isOpen` | `boolean` | | | Whether the menu is open or closed (uncontrolled when omitted) |
| `isPortaled` | `boolean` | | `true` | Whether the menu should be wrapped in a floating portal |
| `isRoot` | `boolean` | | | Whether the menu is a root menu instance. This is set internally by the component based on nesting |
| `minWidth` | `number` | | | Minimum width in pixels for the menu component |
| `onClose` | `((event: Event, closeReason: { type: "backdropClick" \| "itemClick" \| "escapeKeyDown" \| "focusOut"; id?: string; }) => void) \| undefined` | | | Callback function that is called when the menu closes |
| `placement` | `'bottom-end-bottom-start' \| 'bottom-end-top-end' \| 'bottom-middle-top-middle' \| 'bottom-start-bottom-end' \| 'bottom-start-top-start' \| 'middle-end-middle-start' \| 'middle-start-middle-end' \| 'top-end-bottom-end' \| 'top-end-top-start' \| 'top-middle-bottom-middle' \| 'top-start-bottom-start' \| 'top-start-top-end'` | | | The placement of the floating element is determined by two sets of words. The first set of words specifies the point on the anchor element where the floating element will be attached. The second set of coordinates specifies the point on the floating element that will attach to the anchor element. |
| `portalTarget` | `HTMLElement \| null` | | | Portal target element (defaults to `document.body`) |
| `ref` | `any` | | | A ref to apply to the root element. |
| `strategy` | `'absolute' \| 'fixed'` | | | Positioning strategy used by Floating UI |
| `title` | `string` | | | Optional title for nested trigger rendering |
`isRoot` is determined internally by checking for a parent context menu. If no parent exists, the component renders as the root menu; otherwise, a nested menu item trigger is rendered. When `isRoot` is `false`, the `title`, `description`, `icon`, and `isDisabled` props are forwarded to the nested menu item trigger component.
The `placement` prop defaults to `bottom-start` when it is the root menu and `right-start` when it is a nested menu item trigger.
The `strategy` prop is set to `fixed` when the context menu is inside a `Dialog` otherwise it is set to `absolute`.
### Menu.CategoryItem
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `as` | `ElementType<any, keyof IntrinsicElements>` | | | An override of the default HTML tag of the root of the component. Can also be another React component. |
| `children` | `ReactNode` | | | Content displayed inside the category item component |
| `ref` | `any` | | | A ref to apply to the root element. |
### Menu.Divider
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `as` | `ElementType<any, keyof IntrinsicElements>` | | | An override of the default HTML tag of the root of the component. Can also be another React component. |
| `orientation` | `'horizontal' \| 'vertical'` | | `horizontal` | |
| `ref` | `any` | | | A ref to apply to the root element. |
### Menu.Group
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | | | Content displayed inside the group. |
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
### Menu.Item
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `as` | `ElementType<any, keyof IntrinsicElements>` | | | An override of the default HTML tag of the root of the component. Can also be another React component. |
| `children` | `ReactNode` | | | Content displayed inside the menu item component |
| `description` | `ReactNode` | | | Supporting text below title |
| `id` | `string` | | | Unique identifier for the menu item |
| `isDisabled` | `boolean` | | | Whether the menu item is disabled |
| `leadingVisual` | `ReactNode` | | | Leading icon for the menu item component |
| `onClick` | `(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void` | | | Callback function that is called when the menu item is clicked and it closes the menu tree |
| `onFocus` | `(event: FocusEvent<HTMLButtonElement, Element>) => void` | | | Callback function that is called when the menu item is focused |
| `ref` | `any` | | | A ref to apply to the root element. |
| `title` | `ReactNode` | ✅ | | Title of the menu item component |
| `trailingContent` | `ReactNode` | | | |
### Menu.Items
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `as` | `ElementType<any, keyof IntrinsicElements>` | | | An override of the default HTML tag of the root of the component. Can also be another React component. |
| `children` | `ReactNode` | | | Content displayed inside the menu items component |
| `ref` | `any` | | | A ref to apply to the root element. |
### Menu.RadioItem
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `as` | `ElementType<any, keyof IntrinsicElements>` | | | An override of the default HTML tag of the root of the component. Can also be another React component. |
| `children` | `ReactNode` | | | Content displayed inside the menu item component |
| `description` | `ReactNode` | | | Supporting text below title |
| `id` | `string` | | | Unique identifier for the menu item |
| `isChecked` | `boolean` | | `false` | Whether the radio item is checked. Marks the item as aria-checked. |
| `isDisabled` | `boolean` | | | Whether the menu item is disabled |
| `leadingVisual` | `ReactNode` | | | Leading icon for the menu item component |
| `onClick` | `(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void` | | | Callback function that is called when the menu item is clicked and it closes the menu tree |
| `onFocus` | `(event: FocusEvent<HTMLButtonElement, Element>) => void` | | | Callback function that is called when the menu item is focused |
| `ref` | `any` | | | A ref to apply to the root element. |
| `title` | `ReactNode` | ✅ | | Title of the menu item component |
| `trailingContent` | `ReactNode` | | | |
## Accessibility
## Keyboard interactions
Implements the keyboard interactions defined in the [WAI-ARIA menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu/).
| Key | Description |
|-----|-------------|
| `ArrowDown` | Moves focus to the next menu item |
| `ArrowUp` | Moves focus to the previous menu item |
| `ArrowRight` | Opens a submenu when focus is on a nested menu item |
| `ArrowLeft` | Closes the current submenu and returns focus to the parent item |
| `Enter` | Activates the focused menu item |
| `Space` | Activates the focused menu item |
| `Tab` | Closes the menu and moves focus to the next focusable element on the page |
| `Shift + Tab` | Closes the menu and moves focus to the previous focusable element on the page |
| `Escape` | Closes the menu and returns focus to the trigger |
| Type-ahead | Typing a character moves focus to the next item starting with that character |
## WAI-ARIA roles and attributes
The Menu component follows the [WAI-ARIA menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu/).
- The menu container has `role="menu"`
- Menu items have `role="menuitem"`
- `Menu.RadioItem` has `role="menuitemradio"` and `aria-checked`
- `Menu.Group` has `role="group"`
- Nested menu triggers have `role="menuitem"` and manage `aria-expanded` internally
- When the menu opens, focus moves to the first item. When it closes, focus returns to the trigger
- The menu closes when focus leaves the menu area
## Implementation guidelines
- If using a button as a trigger, set `aria-haspopup="menu"` and `aria-expanded` on the trigger button
- When mixing `Menu.RadioItem` with regular items, use `Menu.Group` to separate them. This creates the correct grouping semantics for assistive technologies.
- Only one `Menu.RadioItem` within a group should have `isChecked` set to `true` at a time
- Ensure menu items have descriptive `title` values. These are used for both the visible label and the type-ahead search.
- `role="menu"` only allows children with roles `menuitem`, `menuitemradio`, `menuitemcheckbox`, `group`, or `separator`. The built-in sub-components handle this automatically, but custom content passed without a valid role will break the menu semantics for assistive technologies
### Related WCAG criteria
- [2.1.1 Keyboard](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html) (A): All menu items and submenus are fully operable via keyboard
- [2.4.3 Focus Order](https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html) (A): Focus is managed automatically — first item on open, trigger on close
- [4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) (A): Roles (`menu`, `menuitem`, `menuitemradio`, `group`), states (`aria-checked`, `aria-expanded`), and accessible names are set automatically by the component
## Examples
### Default
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { FilledButton, Menu } from '@neo4j-ndl/react';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'default-menu' }}
>
<Menu.Item
title="Action 1"
onClick={() => {
alert('Action1');
setIsOpen(false);
}}
/>
<Menu.Item
title="Action 2"
onClick={() => {
alert('Action2');
setIsOpen(false);
}}
/>
<Menu.Item
title="Action 3"
description="Action 3 is disabled"
isDisabled={true}
onClick={() => {
alert('Action3');
setIsOpen(false);
}}
/>
<Menu.Item
title="Action 4"
description="Action 4 is awesome"
onClick={() => {
alert('Action4');
setIsOpen(false);
}}
/>
</Menu>
<FilledButton
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Menu
</FilledButton>
</>
);
};
export default Component;
```
### Custom Items
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { FilledButton, Menu, Typography } from '@neo4j-ndl/react';
import { Square2StackIconOutline } from '@neo4j-ndl/react/icons';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
placement="bottom-start-top-start"
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'custom-items-menu' }}
>
<Menu.Item
title={
<Typography variant="display">This can be any element</Typography>
}
/>
<Menu.Item title="Danger action 1" className="n-text-danger-text" />
<Menu.Item
title="Danger action 2"
className="n-text-danger-text"
leadingVisual={
<Square2StackIconOutline className="n-text-danger-icon" />
}
/>
<Menu title="More actions" description="Some additional actions">
<Menu.Item
title="Danger action 3"
className="n-text-danger-text"
isDisabled={true}
/>
<Menu.Item title="Danger action 4" className="n-text-danger-text" />
</Menu>
</Menu>
<FilledButton
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Menu (set with custom placement)
</FilledButton>
</>
);
};
export default Component;
```
### In Dialog
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { Dialog, FilledButton, Menu } from '@neo4j-ndl/react';
import { Square2StackIconOutline } from '@neo4j-ndl/react/icons';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const handleClick = () => setIsModalOpen((prev) => !prev);
const handleClose = () => setIsModalOpen(false);
const handleMenuClick = () => setIsOpen((prev) => !prev);
return (
<>
<div className="n-flex n-justify-center">
<FilledButton onClick={handleClick}>Open Dialog</FilledButton>
</div>
<Dialog
htmlAttributes={{ id: 'modal-root' }}
isOpen={isModalOpen}
onClose={handleClose}
>
<div className="n-flex n-justify-center">
<FilledButton
onClick={handleMenuClick}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Menu
</FilledButton>
</div>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(event, _data) => {
setIsOpen(false);
event?.preventDefault();
event?.stopPropagation();
}}
portalTarget={document.getElementById('modal-root')}
htmlAttributes={{ id: 'dialog-menu' }}
>
<Menu.Item title="Undo" onClick={() => alert('Undo')} />
<Menu.Item title="Redo" isDisabled />
<Menu.Item title="Cut" description="Copy and remove" />
<Menu
title="Copy as"
description="Copying as something"
icon={<Square2StackIconOutline />}
>
<Menu.Item title="Text" />
<Menu.Item title="Video" />
<Menu title="Image">
<Menu.Item title=".png" />
<Menu.Item title=".jpg" />
<Menu.Item title=".svg" />
<Menu.Item title=".gif" />
</Menu>
<Menu.Item title="Audio" />
<Menu title="Share">
<Menu.Item title="Mail" />
<Menu.Item title="Instagram" />
</Menu>
</Menu>
</Menu>
</Dialog>
</>
);
};
export default Component;
```
### In Popover
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { FilledButton, Menu, Popover } from '@neo4j-ndl/react';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'popover-menu' }}
>
<Menu.Item title="Action 1" onClick={() => alert('Action1')} />
<Menu.Item title="Action 2" onClick={() => alert('Action2')} />
<Menu.Item
title="Action 3"
description="Action 3 is disabled"
isDisabled={true}
onClick={() => alert('Action3')}
/>
<Menu.Item
title="Action 4"
description="Action 4 is awesome"
onClick={() => alert('Action4')}
/>
</Menu>
<Popover isOpen={isPopoverOpen}>
<Popover.Trigger>
<FilledButton onClick={() => setIsPopoverOpen(!isPopoverOpen)}>
{isPopoverOpen ? 'Close' : 'Open'} Popover
</FilledButton>
</Popover.Trigger>
<Popover.Content style={{ height: 150, overflow: 'auto', width: 400 }}>
<>
<p>
Scroll me to the bottom to open menu Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
</p>
<FilledButton
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Context Menu
</FilledButton>
</>
</Popover.Content>
</Popover>
</>
);
};
export default Component;
```
### Nested
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { FilledButton, Menu } from '@neo4j-ndl/react';
import { Square2StackIconOutline } from '@neo4j-ndl/react/icons';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'nested-menu' }}
>
<Menu.Item title="Undo" onClick={() => alert('Undo')} />
<Menu.Item title="Redo" isDisabled />
<Menu.Item title="Cut" description="Copy and remove" />
<Menu
title="Copy as"
description="Copying as something"
icon={<Square2StackIconOutline />}
>
<Menu.Item title="Text" />
<Menu.Item title="Video" />
<Menu title="Image">
<Menu.Item title=".png" />
<Menu.Item title=".jpg" />
<Menu.Item title=".svg" />
<Menu.Item title=".gif" />
</Menu>
<Menu.Item title="Audio" />
</Menu>
<Menu title="Share">
<Menu.Item title="Mail" />
<Menu.Item title="Instagram" />
</Menu>
</Menu>
<FilledButton
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Menu
</FilledButton>
</>
);
};
export default Component;
```
### Placements
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { FilledButton, Menu } from '@neo4j-ndl/react';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
placement="middle-end-middle-start"
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'placements-menu' }}
>
<Menu.Item
title="Action 1"
onClick={() => {
alert('Action1');
setIsOpen(false);
}}
/>
<Menu.Item
title="Action 2"
onClick={() => {
alert('Action2');
setIsOpen(false);
}}
/>
<Menu.Item
title="Action 3"
description="Action 3 is disabled"
isDisabled={true}
onClick={() => {
alert('Action3');
setIsOpen(false);
}}
/>
<Menu.Item
title="Action 4"
description="Action 4 is awesome"
onClick={() => {
alert('Action4');
setIsOpen(false);
}}
/>
<Menu.Item
id="action_5"
title="Action 5"
description="Action 5 does not close the menu"
/>
</Menu>
<FilledButton
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Menu
</FilledButton>
</>
);
};
export default Component;
```
### Radio And Normal
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { DropdownButton, Menu, Typography } from '@neo4j-ndl/react';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
const [selectedOption, setSelectedOption] = useState<string | null>(
'option-1',
);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'radio-and-normal-menu' }}
>
<Menu.Group>
<Menu.RadioItem
title="Option 1"
isChecked={selectedOption === 'option-1'}
onClick={() => {
setSelectedOption('option-1');
}}
/>
<Menu.RadioItem
title="Option 2"
isChecked={selectedOption === 'option-2'}
onClick={() => {
setSelectedOption('option-2');
}}
/>
</Menu.Group>
<Menu.Divider />
<Menu.Group>
<Menu.Item
title="Action 1"
onClick={() => {
alert('Action 1');
}}
/>
<Menu.Item
title="Action 2"
onClick={() => {
alert('Action 2');
}}
/>
</Menu.Group>
</Menu>
<div className="n-flex n-flex-col n-gap-token-4 n-items-center">
<Typography variant="body-medium">
Selected option: <strong>{selectedOption}</strong>
</Typography>
<DropdownButton
isOpen={isOpen}
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
>
Open menu
</DropdownButton>
</div>
</>
);
};
export default Component;
```
### Radio Items
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { DropdownButton, Kbd, Menu, Typography } from '@neo4j-ndl/react';
import { MoonIconOutline, SunIconOutline } from '@neo4j-ndl/react/icons';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
const [selectedOption, setSelectedOption] = useState<string | null>(
'option-1',
);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'radio-items-menu' }}
>
<Menu.RadioItem
title="Option 1"
leadingVisual={<SunIconOutline />}
isChecked={selectedOption === 'option-1'}
onClick={() => {
setSelectedOption('option-1');
}}
/>
<Menu.RadioItem
title="Option 2"
leadingVisual={<MoonIconOutline />}
isChecked={selectedOption === 'option-2'}
onClick={() => {
setSelectedOption('option-2');
}}
/>
<Menu.RadioItem
title="Option 3"
description="Option 3 has a description"
isChecked={selectedOption === 'option-3'}
onClick={() => {
setSelectedOption('option-3');
}}
/>
<Menu.RadioItem
title="Option 4"
trailingContent={<Kbd keys={['A']} />}
isChecked={selectedOption === 'option-4'}
onClick={() => {
setSelectedOption('option-4');
}}
/>
</Menu>
<div className="n-flex n-flex-col n-gap-token-4 n-items-center">
<Typography variant="body-medium">
Selected option: <strong>{selectedOption}</strong>
</Typography>
<DropdownButton
isOpen={isOpen}
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
>
Open menu
</DropdownButton>
</div>
</>
);
};
export default Component;
```
### With Category
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { FilledButton, Menu } from '@neo4j-ndl/react';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'category-menu' }}
>
<Menu.Group>
<Menu.CategoryItem>Category 1</Menu.CategoryItem>
<Menu.Item title="Action 1" onClick={() => alert('Action1')} />
<Menu.Item title="Action 2" onClick={() => alert('Action2')} />
</Menu.Group>
<Menu.Divider />
<Menu.Group>
<Menu.Item
title="Action 3"
description="Action 3 is disabled"
isDisabled={true}
onClick={() => alert('Action3')}
/>
<Menu.Item
title="Action 4"
description="Action 4 is awesome"
onClick={() => alert('Action4')}
/>
</Menu.Group>
</Menu>
<FilledButton
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Menu
</FilledButton>
</>
);
};
export default Component;
```
### With Disabled Item
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { FilledButton, Menu } from '@neo4j-ndl/react';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'disabled-item-menu' }}
>
<Menu.Item
title="Action 1"
description="Action 1 is disabled"
isDisabled={true}
/>
<Menu.Item title="Action 2" onClick={() => alert('Action2')} />
</Menu>
<FilledButton
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Menu
</FilledButton>
</>
);
};
export default Component;
```
### With Divider
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { FilledButton, Menu } from '@neo4j-ndl/react';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'divider-menu' }}
>
<Menu.Item title="Action 1" />
<Menu.Divider />
<Menu.Item title="Action 2" />
<Menu.Divider />
<Menu.Item title="Action 3" />
<Menu.Divider />
<Menu.Item title="Action 4" />
</Menu>
<FilledButton
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Menu
</FilledButton>
</>
);
};
export default Component;
```
### With Icon
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { FilledButton, Menu } from '@neo4j-ndl/react';
import { PhotoIconOutline, PlusIconOutline } from '@neo4j-ndl/react/icons';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'icon-menu' }}
>
<Menu.Item
leadingVisual={<PlusIconOutline />}
title="Action 1"
onClick={() => alert('Action1')}
/>
<Menu.Item
leadingVisual={<PlusIconOutline />}
title="Action 2"
onClick={() => alert('Action2')}
/>
<Menu.Item
leadingVisual={<PlusIconOutline />}
title="Action 3"
description="Action 3 is disabled"
isDisabled={true}
onClick={() => alert('Action3')}
/>
<Menu
title="Action 3.1"
description="Action with sub menu"
icon={<PlusIconOutline />}
>
<Menu.Item title="Text" />
<Menu.Item title="Video" />
<Menu title="Image" icon={<PhotoIconOutline />}>
<Menu.Item title=".png" />
<Menu.Item title=".jpg" />
</Menu>
<Menu.Item title="Audio" />
</Menu>
<Menu.Item
leadingVisual={<PlusIconOutline />}
title="Action 4"
description="Action 4 is awesome"
onClick={() => alert('Action4')}
/>
</Menu>
<FilledButton
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Menu
</FilledButton>
</>
);
};
export default Component;
```
### With Keyboard Shortcuts
```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
import { FilledButton, Kbd, Menu } from '@neo4j-ndl/react';
import { PlusIconOutline } from '@neo4j-ndl/react/icons';
import { useRef, useState } from 'react';
const Component = () => {
const anchorEl = useRef<HTMLButtonElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Menu
isOpen={isOpen}
anchorRef={anchorEl}
onClose={(_event, _data) => {
setIsOpen(false);
}}
htmlAttributes={{ id: 'keyboard-shortcuts-menu' }}
>
<Menu.Item
leadingVisual={<PlusIconOutline />}
title="Action 1"
onClick={() => alert('Action1')}
/>
<Menu.Item
leadingVisual={<PlusIconOutline />}
trailingContent={<Kbd keys={['A', ['B']]} />}
title="Action 2"
description="Action 4 has a keyboard shortcut"
onClick={() => alert('Action4')}
/>
</Menu>
<FilledButton
onClick={() => setIsOpen(true)}
ref={anchorEl}
aria-haspopup="menu"
aria-expanded={isOpen}
>
Open Menu
</FilledButton>
</>
);
};
export default Component;
```