Menu
Menu displays a floating list of actions or choices. Pass the trigger element,
describe the rows with items, and handle a selected action with onSelect.
Manti manages positioning, focus, keyboard navigation, and dismissal.
Use Popover instead when the floating panel contains form controls or arbitrary interactive content.
Basic usage
Start with an array of commands. Each command needs a unique value and a
visible label. onSelect receives the selected value.
import { Button, Menu } from '@manti-ui/react'; import type { MenuItem } from '@manti-ui/react'; import { useState } from 'react'; const items: MenuItem[] = [ { value: 'new', label: 'New file' }, { value: 'open', label: 'Open…' }, { value: 'save', label: 'Save', shortcut: '⌘S' }, ]; export default function MenuBasic() { const [selected, setSelected] = useState('Nothing selected'); return ( <> <Menu trigger={<Button variant="tertiary">File</Button>} items={items} onSelect={setSelected} /> <span aria-live="polite">Selected: {selected}</span> </> ); }
Add shortcut to display a keyboard hint. It only displays the hint; register
the actual shortcut separately with useShortcut.
Sizes
size sets the panel's row rhythm: type, padding, and icon size. It matches the
scale every other control uses, so a sm trigger can open a sm menu. Submenus
inherit the size of the menu that opened them.
Groups and item states
Use a group to label related actions and a separator to divide different sets.
An item can also be disabled or use tone: 'danger' for a destructive action.
Useful item properties:
icon: leading visual affordance;shortcut: trailing keyboard hint;disabled: visible but unavailable action;tone: 'danger': destructive action styling;onSelect: handler for only that item.
Checkable items
Checkbox items represent independent settings. Radio items represent one choice
from a set. Both are controlled: pass checked and update application state in
onCheckedChange.
Set closeOnSelect: false when a choice should leave the menu open. Radio
exclusivity comes from application state rather than the group itself.
Submenus
A submenu is another items array nested inside an item. No separate submenu
component is required.
Submenus may contain more submenus. Keep every value unique across the whole
tree. Selecting a leaf closes the complete tree; placement and keyboard routing
are handled automatically. If the submenu label is not plain text, provide
ariaLabel.
Item reference
MenuItem[] accepts these shapes:
| Shape | Use for |
|---|---|
{ value, label } | Action |
{ type: 'checkbox', … } | Independent choice |
{ type: 'radio', … } | One choice from a set |
{ type: 'group', label, items } | Labelled group |
{ type: 'separator' } | Divider |
{ type: 'submenu', value, label, items } | Another level of commands |
State and events
The root onSelect observes every selectable item, including leaves inside a
submenu. An item's own onSelect runs after the root handler.
The root menu is uncontrolled by default. Use open with onOpenChange only
when application state must own whether it is open:
<Menu
open={open}
onOpenChange={setOpen}
trigger={<Button>Account</Button>}
items={items}
/>itemProps adds DOM props to one item. getItemProps applies props to every
action and submenu trigger. Prefer semantic properties such as disabled and
tone before reaching for these lower-level escape hatches.
Menu or Popover?
Use Menu when every row is an action, checkbox, radio, or submenu trigger. Use Popover for ToggleGroup, Input, Select, Slider, forms, explanatory content, or a mixture of controls and commands.
API
| Prop | Type | Default | Description |
|---|---|---|---|
trigger | ReactElement | — | Required element that opens the menu. Cloned with the machine trigger props. |
items | MenuItem[] | — | Required recursive contents: commands, checkbox/radio choices, separators, groups, and `{ type: "submenu", value, label, items }` entries. |
getItemProps | (item: MenuCommand) => MenuItemRootProps | — | Add class, style, ARIA, data attributes, or handlers to item roots. |
contentProps | HTMLAttributes<HTMLDivElement> | — | Props merged onto the floating menu content. |
placement | 'top' | 'bottom' | 'bottom-center' | 'left' | 'right' | '…-start' | '…-end' | 'bottom-start' | Placement relative to the trigger. `bottom-center` is an explicit alias for the centered `bottom` placement. |
size | 'sm' | 'md' | 'lg' | 'md' | Row rhythm of the panel: type, padding, and icon size. Submenus inherit it. |
onSelect | (value: string) => void | — | Called with the value of the selected command. |
open | boolean | — | Controlled open state. |
defaultOpen | boolean | — | Initial open state for uncontrolled usage. |
onOpenChange | (open: boolean) => void | — | Called whenever the open state changes. |
ariaLabel | string | — | Accessible name for the root menu panel. |
Styling
Anatomy
| Part | Selector | Description |
|---|---|---|
content | [data-scope="menu"][data-part="content"] | The translucent dropdown panel holding the items. |
item | [data-scope="menu"][data-part="item"] | A single selectable command. |
trigger-item | [data-scope="menu"][data-part="trigger-item"] | A command that opens a nested menu. |
item-icon | [data-scope="menu"][data-part="item-icon"] | Leading icon or affordance on a command. |
item-text | [data-scope="menu"][data-part="item-text"] | The command label. |
item-shortcut | [data-scope="menu"][data-part="item-shortcut"] | Trailing hint, e.g. a keyboard shortcut. |
item-indicator | [data-scope="menu"][data-part="item-indicator"] | Checked indicator for checkbox and radio items. |
submenu-indicator | [data-scope="menu"][data-part="submenu-indicator"] | Directional affordance shown on a submenu trigger. |
item-group-label | [data-scope="menu"][data-part="item-group-label"] | The heading of a titled group of commands. |
separator | [data-scope="menu"][data-part="separator"] | A divider between groups of items. |
Tokens
| Token | Controls |
|---|---|
--manti-menu-min-width | min width |
--manti-menu-max-width | max width |
--manti-menu-padding | padding |
--manti-menu-gap | gap |
--manti-menu-item-padding-y | item padding y |
--manti-menu-item-padding-x | item padding x |
--manti-menu-item-gap | item gap |
--manti-menu-item-radius | item radius |
--manti-menu-item-font-size | item font size |
--manti-menu-submenu-min-width | submenu min width |
--manti-menu-submenu-indicator-size | submenu indicator size |
--manti-menu-submenu-offset | submenu offset |
--manti-menu-motion-offset | motion offset |
--manti-menu-z-index | z index |
Last updated