Menu
A popup list of actions or options opened from a trigger.
Rows take variant for their look — neutral (the default), accent for the primary action, and
danger for a destructive one.
MenuContent sizes itself by default (sizing="menu"): never narrower than 192px, never wider
than 320px, and at most 384px tall before the list scrolls. sm / md / lg pin the fixed picker
widths (256 / 288 / 384px), anchor matches the trigger’s width, and auto hugs the content.
Show codeHide code
Installation
import {
Menu,
MenuContent,
MenuItem,
MenuSeparator,
MenuTrigger,
} from "@makeplane/propel/components/menu";Usage
import { Button } from "@makeplane/propel/components/button";
import { Icon } from "@makeplane/propel/components/icon";
import {
Menu,
MenuContent,
MenuItem,
MenuSeparator,
MenuTrigger,
} from "@makeplane/propel/components/menu";
import { Copy, ExternalLink, Pencil, Trash2 } from "lucide-react";
export default function BasicDemo() {
return (
<Menu>
<Button
stretch="auto"
variant="secondary"
size="sm"
render={<MenuTrigger />}
label="Actions"
/>
<MenuContent>
<MenuItem variant="accent" icon={<Icon icon={Pencil} />} label="Edit" />
<MenuItem icon={<Icon icon={Copy} tint="secondary" />} label="Make a copy" />
<MenuItem icon={<Icon icon={ExternalLink} tint="secondary" />} label="Open in new tab" />
<MenuSeparator />
<MenuItem variant="danger" icon={<Icon icon={Trash2} />} label="Delete" />
</MenuContent>
</Menu>
);
}Examples
Checkbox items
Multi-select rows built from MenuCheckboxItem. Each row keeps its own checked state and the menu stays open on click. MenuContent’s search prop pins a sticky MenuSearch field above the list; typing filters the rows. The menu focuses the field when it opens; Escape closes the menu from inside it, ArrowDown and ArrowUp move focus into the list, and the query clears when the menu closes — so a reopened menu is unfiltered (resetOnClose={false} keeps a controlled query on purpose).
Show codeHide code
Radio group
Single-select rows wrapped in a MenuRadioGroup sharing one value.
Show codeHide code
With description
A description gives each row a muted second line; passing it switches the row to a taller, top-aligned layout.
Show codeHide code
Shortcuts
The trailing slot pins a Shortcut hint at the row’s inline end. The hint is decorative (aria-hidden) — put the canonical binding on the row as aria-keyshortcuts, and wire the actual key handler yourself; neither part listens for keys.
Show codeHide code
Submenu
MenuSubmenu nests a MenuSubmenuContent behind a MenuSubmenuTrigger row.
Show codeHide code
Link items
MenuLinkItem renders a real anchor, so a row can navigate while keeping menu-item behavior. Marking a row external sets the three things a link out has to carry, together: a trailing arrow, target="_blank", and rel="noreferrer noopener". Rows that stay in the app get none of it, so the arrow means something.
Show codeHide code
Group labels
MenuGroup pairs related rows under a non-interactive MenuLabel heading; a MenuSeparator divides the groups. The heading’s meta slot pins content at its inline end — a count, or a “Clear” action.
Show codeHide code
Footer
MenuContent’s footer prop pins sticky chrome below the role="menu" popup — here a MenuFooter with a hint.
Show codeHide code
Detached triggers
createMenuHandle() links triggers to a Menu defined elsewhere in the tree, so several launch points share one menu without lifting state.
Show codeHide code
Fit height
By default (heightBehavior="fit") a menu that cannot fit its full height shrinks before flipping: it keeps its side while the space there clears a 178px floor (six rows plus the panel’s chrome), scrolls, and flips only when it cannot and the other side can. heightBehavior="flip" restores Base UI’s behavior — jump to the other side of the trigger whenever that side has more room, even when this side held a usable menu. Both menus below open from the same spot in a bounded box; flip jumps above the trigger, the default stays below.
Show codeHide code
API Reference
Menu
The menu Root — Base UI's context/state provider (renders no element of its own). A behavior-only role, so it lives in `components` (rules 1a, 2); the styled parts live in `elements/menu` and are grafted onto Base UI behavior here.
MenuTrigger
The button that opens the menu — Base UI's `Menu.Trigger` passthrough. Propel ships no menu trigger chrome (any control can open a menu), so graft the behavior onto the control you already have via `render` (`<MenuTrigger render={<Button …/>}>`); Base UI contributes the open/close behavior, ARIA wiring, and `data-*` state.
MenuContent
The menu surface: Base UI's portal and positioner wrapped around an elevated, scrollable panel holding the `role="menu"` popup. Optional `search` and `footer` chrome stays pinned outside the scroll area. Rows (`MenuItem`, `MenuGroup`, `MenuSeparator`, …) are its children.
MenuItem
The ready-made selectable menu row: grafts Base UI's `Menu.Item` behavior onto the styled `MenuItem` and lays out a leading single-select check, an optional leading icon, the label, a secondary line, and trailing content.
MenuCheckboxItem
The ready-made toggleable multi-select menu row: grafts Base UI's `Menu.CheckboxItem` behavior onto the styled `MenuCheckboxItem` and lays out the checkbox box (a kept-mounted `Menu.CheckboxItemIndicator` grafted onto `MenuCheckboxItemIndicator` + a lucide `Check`), optional leading icon, label, and end content. Base UI's `Menu.CheckboxItem` tracks the checked state, so `checked` / `defaultChecked` / `onCheckedChange` forward straight to it and the indicator reads it from context.
MenuRadioGroup
Wraps `MenuRadioItem`s sharing one `value` — Base UI's radio-group state, no element chrome.
MenuRadioItem
The ready-made single-select menu row: grafts Base UI's `Menu.RadioItem` behavior onto the styled `MenuRadioItem` and lays out the radio dot (a kept-mounted `Menu.RadioItemIndicator` grafted onto `MenuRadioItemIndicator`), optional leading icon, label, and end content. Wrap rows in a `MenuRadioGroup` carrying `value`/`onValueChange`; the dot reads the selected state from context.
MenuLinkItem
The ready-made navigational `<a>` menu row: grafts Base UI's `Menu.LinkItem` behavior onto the styled `MenuLinkItem` and lays out an optional leading icon, label, and end content.
MenuGroup
Groups related menu items with their `MenuLabel` heading — Base UI's `Menu.Group` passthrough. A structural role with no propel styling of its own, so it lives in `components` (rules 1a, 2).
MenuLabel
A non-interactive section heading for a group of menu items: grafts Base UI's `Menu.GroupLabel` behavior onto the styled `MenuLabel`. Place it inside the `MenuGroup` it names, so Base UI wires it as that group's accessible label.
MenuSeparator
Ready-made divider between groups of menu items: grafts Base UI's `Menu.Separator` behavior (`role="separator"`) onto the styled `MenuSeparator` element.
MenuSubmenu
The submenu Root — Base UI's `SubmenuRoot` context/state provider (renders no element of its own). A behavior-only role, so it lives in `components` (rules 1a, 2).
MenuSubmenuTrigger
The ready-made submenu trigger row: grafts Base UI's `Menu.SubmenuTrigger` behavior onto the styled `MenuSubmenuTrigger` and lays out an optional leading icon, label, end content, and the submenu chevron indicator.
MenuSubmenuContent
The floating surface for a submenu — the same elevated, scrollable panel as `MenuContent`, but anchored beside its parent row instead of below a trigger. Place it inside a `MenuSubmenu`, alongside the `MenuSubmenuTrigger` that opens it.
MenuSearch
A sticky search field pinned above a `MenuContent` popup, via its `search` prop. The menu focuses it on open (it sits outside the `role="menu"` popup — a menu may not own a textbox — so the hand-off is programmatic, not Tab). Text-editing keys stay in the field, which keeps the menu's type-ahead from stealing them; Escape closes the menu, and ArrowDown/ArrowUp move focus into the list — first row down, last row up.
MenuFooter
A non-interactive footer pinned below a menu popup list (sticky chrome outside `role="menu"`).
MenuViewport
The content-morph container for animated menus — Base UI's `Menu.Viewport` grafted onto the shared relative viewport, so swapping popup content (e.g. drill-in panels) can transition sizes.