Menu
A dropdown of actions on Ark UI's Menu — composable parts plus a typesafe data-driven form, with sections, submenus, checkbox / radio items, and a context-menu trigger.
Menu is the kit's dropdown of actions, a styled layer over Ark UI's Menu. Ark owns the open state, positioning, roving focus, type-ahead, and the role="menu" / menuitem ARIA wiring; the kit adds the portaled, animated surface and Mantine-style item props. It ships two APIs from one import — compose the parts for full control, or pass an items array for the common case.
import { Menu } from '@42-central/ui-react/menu';
<Menu.Root>
<Menu.Trigger asChild>
<Button>Open</Button>
</Menu.Trigger>
<Menu.Content>
<Menu.Item startSection={<User />} onClick={openProfile}>Profile</Menu.Item>
<Menu.Divider />
<Menu.Item color="red" startSection={<Trash />} onClick={remove}>Delete</Menu.Item>
</Menu.Content>
</Menu.Root>Playground
Menu
Dir
Props
Code
<Menu
position="bottom-start"
offset={8}
closeOnSelect
loopFocus={false}
/>Basic usage
Menu.Item takes startSection / endSection (RTL-aware, like Mantine's left/right sections) and a color for destructive actions. Menu.Label heads a section and Menu.Divider separates them. The dropdown is portaled (escapes overflow: hidden ancestors) and animated.
Code
<Menu.Root>
<Menu.Trigger asChild>
<Button endSlot={<ChevronDown />}>Account</Button>
</Menu.Trigger>
<Menu.Content>
<Menu.Label>Application</Menu.Label>
<Menu.Item startSection={<User />}>Profile</Menu.Item>
<Menu.Item startSection={<Settings />} endSection={<span>⌘S</span>}>Settings</Menu.Item>
<Menu.Item startSection={<Users />}>Team</Menu.Item>
<Menu.Divider />
<Menu.Item startSection={<LogOut />}>Log out</Menu.Item>
<Menu.Item color="red" startSection={<Trash2 />}>Delete account</Menu.Item>
</Menu.Content>
</Menu.Root>Data-driven
For simple action menus, describe the whole thing as a data array — built on the same parts, the way Autocomplete is built on Combobox. Each item's onClick fires on activation (click and keyboard); a bare string is shorthand for { label, value }, { type: 'divider' } is a separator, and { group, items } is a section. A group takes an optional decorative prefix (the code-themed // leader, off by default; hidden from screen readers and the clipboard). The component is generic over const data, so an item's explicit value narrows the optional top-level onSelect(value).
Code
<Menu
data={[
{ label: 'Profile', value: 'profile', startSection: <User /> },
{ label: 'Settings', value: 'settings', startSection: <Settings /> },
{ type: 'divider' },
{ group: 'Danger zone', prefix: '// ', items: [
{ label: 'Delete', value: 'delete', color: 'red', startSection: <Trash2 /> },
]},
]}
onSelect={(value) => setLast(value)} // value: 'profile' | 'settings' | 'delete'
>
<Button endSlot={<ChevronDown />}>Actions</Button>
</Menu>Link items
An item that navigates should be a real link, so middle-click, "open in new tab", the URL on hover and the link role all work. Pass your router's link component as the item's only child with asChild: the item becomes that link, and startSection, endSection, color and the highlight still apply. Keyboard selection (Enter) clicks the link, so client-side routing keeps working. Every item part composes the same way: Menu.CheckboxItem, Menu.RadioItem, Menu.SubTrigger and Menu.ItemGroupLabel included.
Code
import { Link as RouterLink } from 'your-router';
<Menu.Content>
<Menu.Item asChild startSection={<BookOpen />}>
<RouterLink href="/docs/breadcrumb">Breadcrumb docs</RouterLink>
</Menu.Item>
<Menu.Item asChild startSection={<BookOpen />}>
<RouterLink href="/docs/alert">Alert docs</RouterLink>
</Menu.Item>
<Menu.Divider />
<Menu.Item asChild startSection={<Link />} endSection={<ExternalLink />}>
<a href="https://ark-ui.com/docs/components/menu" target="_blank" rel="noreferrer">
Ark UI Menu
</a>
</Menu.Item>
</Menu.Content>Submenus
Nest a Menu.Sub and open it from a Menu.SubTrigger; its dropdown is a Menu.SubContent (same surface as Menu.Content). In the data-driven form, an item with both a label and its own items becomes a submenu automatically.
Code
<Menu.Content>
<Menu.Item startSection={<Copy />}>Copy</Menu.Item>
<Menu.Item startSection={<Scissors />}>Cut</Menu.Item>
<Menu.Divider />
<Menu.Sub>
<Menu.SubTrigger startSection={<Share2 />}>Share</Menu.SubTrigger>
<Menu.SubContent>
<Menu.Item startSection={<Link />}>Copy link</Menu.Item>
<Menu.Item startSection={<Mail />}>Email</Menu.Item>
</Menu.SubContent>
</Menu.Sub>
</Menu.Content>Checkbox & radio
Menu.CheckboxItem and Menu.RadioGroup / Menu.RadioItem carry selection state. Set closeOnSelect={false} on Menu.Root to keep the dropdown open while toggling several options.
Code
<Menu.Root closeOnSelect={false}>
<Menu.Trigger asChild><Button endSlot={<ChevronDown />}>View</Button></Menu.Trigger>
<Menu.Content>
<Menu.Label>Editor</Menu.Label>
<Menu.CheckboxItem value="wrap" checked={wrap} onCheckedChange={setWrap}>Word wrap</Menu.CheckboxItem>
<Menu.CheckboxItem value="minimap" checked={minimap} onCheckedChange={setMinimap}>Minimap</Menu.CheckboxItem>
<Menu.Divider />
<Menu.Label>Theme</Menu.Label>
<Menu.RadioGroup value={theme} onChange={setTheme}>
<Menu.RadioItem value="light">Light</Menu.RadioItem>
<Menu.RadioItem value="dark">Dark</Menu.RadioItem>
<Menu.RadioItem value="system">System</Menu.RadioItem>
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>Context menu
Swap Menu.Trigger for Menu.ContextTrigger to open on right-click (and long-press on touch).
Code
<Menu.Root>
<Menu.ContextTrigger asChild>
<div>Right-click here</div>
</Menu.ContextTrigger>
<Menu.Content>
<Menu.Item startSection={<Copy />}>Copy</Menu.Item>
<Menu.Item startSection={<Scissors />}>Cut</Menu.Item>
<Menu.Divider />
<Menu.Item color="red" startSection={<Trash2 />}>Delete</Menu.Item>
</Menu.Content>
</Menu.Root>Anatomy
Menu.Root— wraps Ark'sMenu.Root; owns open state + positioning.Menu.Trigger— the button that opens the menu; useasChildto wrap your own element.Menu.ContextTriggeropens on right-click instead.Menu.Content— the portaled, animated dropdown.Menu.Item— an action row, withstartSection/endSection/color.Menu.Label/Menu.Divider— a section heading and a separator. For ARIA-grouped sections useMenu.ItemGroup+Menu.ItemGroupLabel. Both headings accept an optional decorativeprefix(e.g."// ", off by default).Menu.CheckboxItem,Menu.RadioGroup/Menu.RadioItem— stateful selection rows.Menu.Sub/Menu.SubTrigger/Menu.SubContent— a nested submenu.
API
Menu.Root props (beyond Ark's Menu.Root):
Prop
Type
Every rendered part — Menu.Content, Menu.Item, Menu.CheckboxItem, Menu.RadioItem,
Menu.SubTrigger, Menu.Label, Menu.ItemGroupLabel, Menu.Divider — also takes its own
testId prop, rendered as data-testid on that part. See the Testing guide.
Menu.Item props (beyond Ark's Menu.Item):
Prop
Type
<Menu data> (data-driven) props:
Prop
Type
Each entry also accepts an optional testId, defaulting to its resolved value (explicit or
auto-generated) — see the Testing guide.
Changelog
- #288
Menu.Item,CheckboxItem,RadioItem,SubTriggerandItemGroupLabelrender as your router link underasChild, with their sections, indicator and highlight - #288
Pressing Enter on a link item navigates client-side instead of reloading the page
- #287
A
Menubuilt from a non-literalitemsarray typesonSelect's value asstringinstead ofany
- #251
An outside click no longer dismisses the parent while a nested overlay is open
- #135
Opened inside a
ModalorDrawer, an overlay portals into it, so it always paints above its host
- #37
Borders and dividers on floating panels match the panel's own surface
- #4
New
Menu: composable parts or a data-driven wrapper, with submenus, sections and RTL-aware placement
Notifier
Spawn toast notifications imperatively from anywhere — success/error/warning/info/loading, actions, promises and in-place updates — with createNotifier. A custom toaster modelled on the overlays manager, animated in CSS.
Popover
A floating panel on Ark UI's Popover — portaled, width-matched to the trigger, animated, with composable Header / Body / Footer slots.