Overlays@knitui/components
Menu
Menu is a compound component built on top of Popover. Compose Menu.Target (the trigger element), Menu.Dropdown (the panel), Menu.Item (an interactive row), Menu.Label (a section header), and Menu.Divider (a separator). State can be uncontrolled (defaultOpened) or fully controlled (opened + onChange).
import { Menu } from "@knitui/components";Usage
Menu.Target wraps the trigger, Menu.Dropdown holds the items. The target keeps
its own props — the menu only adds the press handling.
import { Button, Menu } from "@knitui/components";
<Menu>
<Menu.Target>
<Button>Actions</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Document</Menu.Label>
<Menu.Item onPress={rename}>Rename</Menu.Item>
<Menu.Item onPress={duplicate}>Duplicate</Menu.Item>
<Menu.Divider />
<Menu.Item theme="red" onPress={remove}>
Delete
</Menu.Item>
</Menu.Dropdown>
</Menu>;Uncontrolled by default. Pass opened + onChange to control it, or use
defaultOpened for the initial state only.
Items take leftSection / rightSection for icons and shortcut hints. A menu is
for actions; if the dropdown is for choosing a value, you want a select.
Positioning goes through the kit's own floating
engine: position, offset and the arrow*
props behave the same as on Popover.
When to use something else
- Choosing a value from a list →
SelectorCombobox - Rich anchored content rather than a list of commands →
Popover - A label that appears on hover →
Tooltip
Examples
Controlled
Fully controlled open state driven by React.useState — demonstrates the Trigger pattern.
Loading example…
With Labels And Divider
Labels and a divider group related items into named sections.
Loading example…
With Sections
Left and right sections hold icons or badges on either side of the label.
Loading example…
Disabled Items
Disabled items are visually muted and do not respond to press events.
Loading example…
Disabled Menu
The whole menu can be disabled — the dropdown never renders.
Loading example…
With Arrow
The arrow variant draws a pointer from the dropdown toward the target.
Loading example…
Styles
Per-slot styles targets individual parts — here the label, item, and itemLabel.
Loading example…
Shadows
All five shadow levels side by side to compare dropdown elevation. Each menu is always open and rendered in its own contained relative wrapper (withinPortal={false}) so the dropdowns sit inline and don't overlap.
Loading example…
Hover Trigger
Hover trigger opens the menu on pointer enter and closes on pointer leave.
Loading example…
Props
| Prop | Type | Default | Description |
|---|---|---|---|
arrowOffset | number | undefined | 5 | Arrow distance from the start/end edge when `arrowPosition="side"`. |
arrowPosition | ArrowPosition | undefined | 'side' | Arrow alignment: centered, or pinned toward an aligned placement. |
arrowRadius | number | undefined | 0 | Corner radius of the arrow's outward corner in px. |
arrowSize | number | undefined | 7 | Arrow square edge length in px. |
children | ReactNode | — | `Menu.Target` + `Menu.Dropdown`. |
closeDelay | number | undefined | 100 | Close delay in ms for hover triggers. |
closeOnClickOutside | boolean | undefined | true | Close on outside press (web). |
closeOnEscape | boolean | undefined | true | Close on `Escape` (web). |
closeOnItemClick | boolean | undefined | true | Close the menu when an item is pressed. |
defaultOpened | boolean | undefined | false | Uncontrolled initial opened state. |
disabled system | boolean | undefined | false | Skip rendering the dropdown entirely. |
keepMounted | boolean | undefined | false | Keep the dropdown mounted while closed. |
loop | boolean | undefined | true | Wrap Arrow-key focus at the ends of the item list (web). |
menuItemTabIndex | 0 | -1 | undefined | -1 | `tabIndex` set on every item. Leave at `-1`: items are reached with the Arrow keys (roving focus), which is the ARIA menu pattern, and `-1` keeps them programmatically focusable without putting every item in the page's tab order. `0` additionally makes each item a Tab stop — occasionally wanted for a menu used as a plain link list. |
offset | number | undefined | 8 | Gap between target and dropdown in px. |
onChange | ((opened: boolean) => void) | undefined | — | Called when the opened state changes. |
onClose | (() => void) | undefined | — | Called when the menu closes. |
onOpen | (() => void) | undefined | — | Called when the menu opens. |
openDelay | number | undefined | 0 | Open delay in ms for hover triggers. |
opened | boolean | undefined | — | Controlled opened state. |
position | Placement | undefinedtop · top-start · top-end · bottom · bottom-start · bottom-end · left · left-start · left-end · right · right-start · right-end | 'bottom-start' | Dropdown placement relative to the target. |
radius system | RadiusTokens | undefined | — | Dropdown border radius. |
returnFocus | boolean | undefined | — | Return focus to the trigger when the menu closes (web). Defaults to `true` for click triggers and `false` for hover triggers: a menu opened by pointing at something never took focus in the first place, so "returning" it would move focus somewhere the user never put it. |
shadow system | "xs" | "sm" | "md" | "lg" | "xl" | "xxs" | "xxl" | undefinedxs · sm · md · lg · xl | 'md' | Dropdown shadow scale. |
styles system | SlotStyles<MenuStyles> | undefined | — | Per-slot style sugar — props spread onto the matching styled part. |
theme system | ThemeName | null | undefined | — | Accent theme applied to the menu subtree (trigger + dropdown). |
trapFocus | boolean | undefined | — | Trap Tab within the open dropdown (web). Defaults to `true` for click triggers, `false` for hover triggers — see `returnFocus`. |
trigger | MenuTrigger | undefinedclick · hover · click-hover | 'click' | Event that opens the menu. |
width | PopoverWidth | undefined | 'max-content' | Dropdown width; `'target'` matches the target. |
withArrow | boolean | undefined | false | Render an arrow pointing at the target. |
withinPortal | boolean | undefined | true | Render the dropdown in a portal. |
zIndex | number | undefined | 300 | Dropdown `z-index`. |
Style slots
Every part of Menu can be styled through the styles prop. Explicit props on the component always win over slot styles.
| Slot | Targets |
|---|---|
divider | Props spread onto every divider (Menu.Divider). |
dropdown | Props spread onto the dropdown frame (Menu.Dropdown). |
item | Props spread onto every item frame (Menu.Item). |
itemLabel | Props spread onto every item's label text node. |
itemSection | Props spread onto every item's left/right section wrappers. |
label | Props spread onto every label (Menu.Label). |
Accessibility
Menu implements the ARIA menu-button pattern. The dropdown is a role="menu",
each item a role="menuitem", and focus is managed: opening moves focus to the
first item, Tab is trapped inside the dropdown, and closing returns focus
to the trigger.
Items are reached with the arrow keys — roving focus — so they stay out of the page's tab order rather than adding one tab stop per menu entry.
| Key | Action |
|---|---|
| ↓ / ↑ on the trigger | Open the menu, focusing the first / last item |
| Enter / Space on the trigger | Open the menu |
| ↓ / ↑ in the menu | Next / previous item, wrapping (loop) |
| Home / End | First / last item |
| Enter / Space | Activate the focused item |
| Esc | Close and return focus to the trigger |
Disabled items are skipped by arrow navigation and are never focused.
Hover menus deliberately do not manage focus. With trigger="hover" or
"click-hover", opening the menu does not take focus and closing it does not move
focus to the trigger — a menu you opened by pointing at something never had focus, so
"returning" it would move the caret somewhere you never put it. Keyboard users reach
a hover menu the same way as any other: by pressing or arrowing on the trigger.
Override with trapFocus / returnFocus if your case differs.
loop={false}stops arrow focus wrapping at the ends.menuItemTabIndex={0}additionally makes every item a Tab stop. Rarely wanted — the arrow keys are the menu pattern — but useful if you are usingMenuas a plain list of links.- Give an icon-only trigger an accessible name (
aria-labelon the target, or useActionIcon, which is built for it).