Menu
A list of actions in a popup that grows from its trigger. Base UI provides the behavior: focus, arrow keys, typeahead, dismissal and positioning away from the window edges.
- Behavior from Base UI
- 2.0 KB gzipped · script and styles
@orbex/sonar-components/menu
Examples
Code
import { Button } from '@orbex/sonar-components/button'
import { Menu } from '@orbex/sonar-components/menu'
import type { ComponentProps } from 'react'
import copySymbol from '@material-symbols/svg-400/rounded/content_copy.svg?raw'
import deleteSymbol from '@material-symbols/svg-400/rounded/delete.svg?raw'
import downloadSymbol from '@material-symbols/svg-400/rounded/download.svg?raw'
import expandSymbol from '@material-symbols/svg-400/rounded/keyboard_arrow_down.svg?raw'
import printSymbol from '@material-symbols/svg-400/rounded/print.svg?raw'
import shareSymbol from '@material-symbols/svg-400/rounded/share.svg?raw'
/* Material Symbols Rounded, weight 400, as icon components (Foundations › Icons) */
const symbol = (svg: string) => {
const path = svg.match(/ d="([^"]+)"/)?.[1]
return (props: ComponentProps<'svg'>) => (
<svg viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true" focusable="false" {...props}>
<path d={path} />
</svg>
)
}
const CopyIcon = symbol(copySymbol)
const DeleteIcon = symbol(deleteSymbol)
const DownloadIcon = symbol(downloadSymbol)
const ExpandIcon = symbol(expandSymbol)
const PrintIcon = symbol(printSymbol)
const ShareIcon = symbol(shareSymbol)
export default function Actions() {
return (
<Menu.Root>
<Menu.Trigger render={<Button variant="outlined" iconEnd={<ExpandIcon />} />}>Actions</Menu.Trigger>
<Menu.Content>
<Menu.Item icon={<DownloadIcon />} trailing="CSV">
Export
</Menu.Item>
<Menu.Item icon={<PrintIcon />}>Print</Menu.Item>
<Menu.Item icon={<ShareIcon />}>Share link</Menu.Item>
<Menu.Item icon={<CopyIcon />}>Duplicate order</Menu.Item>
<Menu.Separator />
<Menu.Item icon={<DeleteIcon />} tone="danger">
Cancel order
</Menu.Item>
</Menu.Content>
</Menu.Root>
)
}
Code
import { IconButton } from '@orbex/sonar-components/icon-button'
import { Menu } from '@orbex/sonar-components/menu'
import { useState } from 'react'
import type { ComponentProps } from 'react'
import moreSymbol from '@material-symbols/svg-400/rounded/more_vert.svg?raw'
import refreshSymbol from '@material-symbols/svg-400/rounded/refresh.svg?raw'
/* Material Symbols Rounded, weight 400, as icon components (Foundations › Icons) */
const symbol = (svg: string) => {
const path = svg.match(/ d="([^"]+)"/)?.[1]
return (props: ComponentProps<'svg'>) => (
<svg viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true" focusable="false" {...props}>
<path d={path} />
</svg>
)
}
const MoreIcon = symbol(moreSymbol)
const RefreshIcon = symbol(refreshSymbol)
export default function Units() {
const [unit, setUnit] = useState('plt')
return (
<Menu.Root>
<Menu.Trigger render={<IconButton label="Inventory options" icon={<MoreIcon />} />} />
<Menu.Content align="end">
<Menu.Group>
<Menu.GroupLabel>Show figures in</Menu.GroupLabel>
<Menu.RadioGroup value={unit} onValueChange={(value) => setUnit(String(value))}>
<Menu.RadioItem value="plt">Pallets</Menu.RadioItem>
<Menu.RadioItem value="kg">Kilograms</Menu.RadioItem>
<Menu.RadioItem value="items">Items</Menu.RadioItem>
</Menu.RadioGroup>
</Menu.Group>
<Menu.Separator />
<Menu.Item icon={<RefreshIcon />}>Refresh stock</Menu.Item>
</Menu.Content>
</Menu.Root>
)
}
Sizes, variants and states
Usage
- For actions on the thing in view. Choices that change what is shown can live in a menu as a radio group.
- Destructive actions go last, after a separator, in the danger tone. They still need a confirmation or an undo.
- The trigger keeps its look: pass a Button or an IconButton with
render.
Do and don’t
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Enter, Space or Down arrow | On the trigger: opens the menu and focuses the first item. |
| Up and Down arrows | Move between items; disabled items are skipped. |
| Home and End | Go to the first or the last item. |
| A letter | Moves to the next item that starts with it. |
| Enter or Space | Activates the item, or chooses it in a group of choices. |
| Escape | Closes the menu and returns focus to the trigger. |
| Tab | Closes the menu and moves on. |
Roles and attributes
| Element or attribute | What it gives |
|---|---|
aria-haspopup, aria-expanded |
On the trigger: it opens a menu, and whether it is open. |
role="menu" |
The popup; items are menuitem, or menuitemradio with aria-checked in a group of choices. |
| Group labels | Name their group, read when focus enters it. |
Notes
- The trigger announces that it opens a menu, and whether it is open.
- Arrow keys move between items, typing jumps to an item, Escape closes and returns focus to the trigger.
- The highlight stays neutral behind danger items, so their red text keeps 4.5:1.
Use it
Each component is its own entry point with its own stylesheet, so an app loads only what it uses. The tokens and the cascade layers are set once for the app: see Installation.
import { Menu } from '@orbex/sonar-components/menu'
import '@orbex/sonar-components/menu.css'
API
Menu.Root
Holds the menu’s open state.
Passes every other prop to Base UI: see the Base UI reference.
Menu.Trigger
The element that opens the menu. Pass a Button or an IconButton with render, so the trigger keeps its look.
Passes every other prop to Base UI: see the Base UI reference.
Menu.Content
The popup, positioned against the trigger and rendered at the end of the document.
| Prop | Type | Default |
|---|---|---|
className
Classes added to the menu’s popup, after sonar-menu: a string, or a function of the menu’s state. |
ClassName
|
— |
side
Side of the trigger the menu opens on. It flips when there is no room. |
BaseMenu.Positioner.Props['side']
|
'bottom' |
align
How the menu lines up with its trigger along that side: start, center or end. A menu in a card header aligns to the end, so it opens inward. |
BaseMenu.Positioner.Props['align']
|
'start' |
sideOffset
Distance from the trigger, in pixels. |
number
|
6 |
Passes every other prop to Base UI: see the Base UI reference.
Menu.Item
An action.
| Prop | Type | Default |
|---|---|---|
className
Classes added to the item, after its own: a string, or a function of its state. |
ClassName
|
— |
icon
Icon before the label. |
ReactNode
|
— |
trailing
Text at the end of the row, such as a shortcut or a count. |
ReactNode
|
— |
tone
Danger for destructive actions: the label turns red, and the action should still be confirmed or undoable. |
'default' | 'danger'
|
'default' |
Passes every other prop to Base UI: see the Base UI reference.
Menu.Separator
A rule between groups of items.
| Prop | Type | Default |
|---|---|---|
className
Classes added to the separator, after its own: a string, or a function of its state. |
ClassName
|
— |
Passes every other prop to Base UI: see the Base UI reference.
Menu.Group
Items that belong together, named by a GroupLabel.
Passes every other prop to Base UI: see the Base UI reference.
Menu.GroupLabel
The name of a group, read when focus enters it.
| Prop | Type | Default |
|---|---|---|
className
Classes added to the group label, after its own: a string, or a function of its state. |
ClassName
|
— |
Passes every other prop to Base UI: see the Base UI reference.
Menu.RadioGroup
A set of items where exactly one is chosen, such as the unit figures are shown in.
Passes every other prop to Base UI: see the Base UI reference.
Menu.RadioItem
One choice in a RadioGroup, with a check when chosen.
| Prop | Type | Default |
|---|---|---|
className
Classes added to the radio item, after its own: a string, or a function of its state. |
ClassName
|
— |
Passes every other prop to Base UI: see the Base UI reference.