Menu
A dropdown list of actions, with groups, submenus and checkbox or radio selection.
One menu, two triggers. Use the default MenuTrigger when a visible control opens it
(a button, an avatar, a "⋯"); use MenuContextTrigger when the thing you
open from is a region you right-click. There is no separate context-menu component: swapping
the trigger is the whole difference, so everything on this page applies to both.
Usage
import {
Menu,
MenuContent,
MenuItem,
MenuSeparator,
MenuShortcut,
MenuTrigger,
} from "@kanzo-tech/ui";<Menu>
<MenuTrigger asChild>
<Button variant="outline">Actions</Button>
</MenuTrigger>
<MenuContent>
<MenuItem value="claim">Claim</MenuItem>
<MenuItem value="abandon" variant="destructive">Abandon</MenuItem>
</MenuContent>
</Menu>Every item needs a value — it is the id the menu reports through onSelect and the key
typeahead navigation matches against.
Anatomy
MenuContent renders the portal and the positioner for you. MenuPositioner is that positioner,
exported for a menu whose floating layer you assemble yourself.
Menu
├── MenuTrigger (or MenuContextTrigger — one or the other)
└── MenuContent (portal + positioner)
├── MenuGroup
│ ├── MenuGroupLabel
│ └── MenuItem
│ └── MenuShortcut
├── MenuCheckboxItem
├── MenuRadioGroup
│ └── MenuRadioItem
├── MenuSeparator
├── MenuArrow (optional — the menu draws none by default)
└── MenuSub
├── MenuSubTrigger
└── MenuSubContentMenuArrow is the same part Popover, Tooltip and HoverCard ship, under the menu machine:
place it inside MenuContent when the menu should point at what opened it.
Groups
MenuGroup takes a heading string and renders the MenuGroupLabel for you.
Checkbox items
Set closeOnSelect={false} on the root when the menu is a set of toggles — otherwise it shuts
after the first one and the user has to reopen it for each column.
Radio group
Submenu
Context trigger
A context menu is this same Menu with one part swapped: MenuContextTrigger opens on
right-click at the pointer, where MenuTrigger anchors to a control. Nothing else changes — the
content, the items, the groups, the submenus and the shortcuts are the parts above, so anything
true of MenuItem is true of an item in a context menu.
import { Menu, MenuContent, MenuContextTrigger, MenuItem } from "@kanzo-tech/ui";<Menu>
<MenuContextTrigger asChild>
<div>Q-1041 · Something is eating the bell-ropes</div>
</MenuContextTrigger>
<MenuContent>
<MenuItem value="claim">Claim</MenuItem>
</MenuContent>
</Menu>Reach for it when the target is an area rather than a control — a row, a canvas, a file in a
tree. But right-click is not reachable by keyboard on its own, so a context menu should never be
the only route to an action: mirror the items in a toolbar Menu.
Keyboard
| Key | Does |
|---|---|
| ↓ / ↑ | On the trigger, opens the menu with the first / last item highlighted; inside, moves the highlight. |
| Home / End | Highlights the first / last item. |
| Enter / Space | Runs the highlighted item and closes the menu, or opens its submenu when the item is a MenuSubTrigger. |
| A–Z | Typeahead: highlights the next item whose text starts with what you have typed. Space joins the query once one is in progress. |
| → | Opens the highlighted item's submenu and moves into it. |
| ← | Closes a submenu and returns the highlight to the parent menu. |
| Esc | Closes the menu and returns focus to the trigger. |
A checkbox or radio item toggles under Enter like any other item, and closes the menu
with it unless you pass closeOnSelect={false}.
API Reference
menuContentVariants is exported for a surface that should read as a menu without being one.
Menu
| Prop | Type | Default |
|---|---|---|
positioning | PositioningOptions | { placement: "bottom-end" } |
lazyMount | boolean | true |
unmountOnExit | boolean | true |
Extends React.ComponentProps<typeof ArkMenu.Root>.
MenuTrigger / MenuContextTrigger
Neither takes props of its own; both extend their Ark part (ArkMenu.Trigger,
ArkMenu.ContextTrigger) and both take asChild. MenuContextTrigger adds cursor-default, so
the region it wraps does not read as a link or a button — the affordance is the right-click. Use
one or the other, not both.
MenuItem
| Prop | Type | Default |
|---|---|---|
variant | "default" | "destructive" | "default" |
MenuQuickItem is the same item stacked vertically with a larger icon, for the grid-style
"quick action" rows.
MenuGroup / MenuRadioGroup
| Prop | Type | Default |
|---|---|---|
heading | string | — |
Notes
MenuContent caps itself at a fixed max-h-96 rather than the floating-ui available-height
variable. Clamping to the measured space feeds the element's own resize back into the
positioner — content shrinks, the scrollbar toggles, the available space changes, recompute —
and that loop hard-freezes the tab. A fixed cap is decoupled from the measurement.
There is no ContextMenu family. A context menu is a Menu whose trigger is a
MenuContextTrigger — the only part a right-click surface actually needs, since every item,
separator and group below it is the same part either way. A parallel family would have been nine
data-slot renames and one real trigger.