Kanzo UI
Overlays & feedback

Menu

A dropdown list of actions, with groups, submenus and checkbox or radio selection.

Ark UI

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.

A menu holds commands, not a value. If closing the surface leaves state behind, you wanted a listbox — Select or Combobox. See the rule.

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
        └── MenuSubContent

MenuArrow 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

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

KeyDoes
↓ / ↑On the trigger, opens the menu with the first / last item highlighted; inside, moves the highlight.
Home / EndHighlights the first / last item.
Enter / SpaceRuns the highlighted item and closes the menu, or opens its submenu when the item is a MenuSubTrigger.
A–ZTypeahead: 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.
EscCloses 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.

PropTypeDefault
positioningPositioningOptions{ placement: "bottom-end" }
lazyMountbooleantrue
unmountOnExitbooleantrue

Extends React.ComponentProps<typeof ArkMenu.Root>.

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.

PropTypeDefault
variant"default" | "destructive""default"

MenuQuickItem is the same item stacked vertically with a larger icon, for the grid-style "quick action" rows.

PropTypeDefault
headingstring—

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.

On this page