Components

Dropdown Menu

Displays a menu of actions or options triggered by a button, with full support for icons, shortcuts, checkbox and radio items, and nested submenus.

Basic usage

Wrap a trigger and a content panel of items. Use asChild to render the trigger as a Button.

Checkbox & radio items

Use DropdownMenuCheckboxItem for independent toggles and DropdownMenuRadioGroup for mutually exclusive choices.

Both are controlled only. The Radix menu primitives read checked (defaulting to false) and value; unlike the standalone Checkbox and RadioGroup they have no defaultChecked or defaultValue. Passing one type-checks — React allows those attributes on any element — but it lands on the underlying <div> and the row never ticks. Hold the state yourself and pass onCheckedChange / onValueChange.

Submenus

Nest a DropdownMenuSub to group related actions behind a hover-expandable trigger.

The package renders DropdownMenuSubContent inside its own portal. That is not cosmetic: DropdownMenuContent carries overflow-x-hidden overflow-y-auto so a long menu can scroll, and its open animation leaves a transform in place, which makes the panel a containing block even for position: fixed children. Without the portal the submenu opens beside the parent panel and is clipped away entirely — the chevron row reacts to nothing, with no error and no warning.

Platform-aware shortcuts

DropdownMenuShortcut is a plain right-aligned span, so whatever glyph you type is what every user sees — is simply wrong on Windows and Linux. Drop KeyboardHint into the row instead and write mod for the primary modifier: it renders on macOS and Ctrl everywhere else. It reports non-Mac on the first render so server and client HTML match, then corrects itself after hydration.

Compact density

Menu rows measure their vertical padding with py-(--menu-item-py) rather than a fixed py-1.5, so they follow the density axis: 6px at the default setting, 4px under data-density="compact". Only the vertical axis moves — compact means more rows on screen, not text jammed against the panel edge. In an app the attribute is set once on <html> by DensityProvider from @comitor/ui/shell; the panels below scope it by hand so the two can sit side by side.

Props

The parts exported from @comitor/ui. Every part forwards the props of the Radix primitive it wraps, plus the native props of the element it renders.

PropTypeDefaultDescription
DropdownMenuDropdownMenuPrimitive.RootRoot wrapper that owns the open state.
DropdownMenuTriggerDropdownMenuPrimitive.TriggerElement that opens the menu. Pass asChild to render your own Button.
DropdownMenuContentDropdownMenuPrimitive.ContentThe floating panel. Always portalled to document.body by the package.
DropdownMenuPortalDropdownMenuPrimitive.PortalEscape hatch for portalling content yourself — Content and SubContent already portal, so you rarely need it.
DropdownMenuGroupDropdownMenuPrimitive.GroupGroups related items so assistive tech announces them together.
DropdownMenuItemDropdownMenuPrimitive.ItemA selectable action row. See the item props below.
DropdownMenuCheckboxItemDropdownMenuPrimitive.CheckboxItemToggle row with a check indicator. Controlled only — checked (default false) plus onCheckedChange; there is no defaultChecked.
DropdownMenuRadioGroupDropdownMenuPrimitive.RadioGroupGroups radio items and tracks the selected value. Controlled only — value plus onValueChange; there is no defaultValue.
DropdownMenuRadioItemDropdownMenuPrimitive.RadioItemSingle-choice row with a dot indicator; requires value.
DropdownMenuLabelDropdownMenuPrimitive.LabelNon-interactive section heading. Accepts inset.
DropdownMenuSeparatorDropdownMenuPrimitive.SeparatorFull-bleed divider drawn with the decorative border token.
DropdownMenuShortcutspanMuted, right-aligned shortcut text. Use KeyboardHint instead when the combo contains a modifier.
DropdownMenuSubDropdownMenuPrimitive.SubWraps a nested submenu (SubTrigger + SubContent).
DropdownMenuSubTriggerDropdownMenuPrimitive.SubTriggerRow that opens a submenu; accepts inset and appends a chevron.
DropdownMenuSubContentDropdownMenuPrimitive.SubContentThe submenu panel, portalled so the parent panel cannot clip it.

DropdownMenu (root)

PropTypeDefaultDescription
openbooleanControlled open state of the menu. Omit for uncontrolled behavior.
defaultOpenbooleanfalseOpen state when initially rendered, for uncontrolled usage.
onOpenChange(open: boolean) => voidEvent handler called when the open state changes.
modalbooleantrueWhether the menu traps focus and blocks outside interaction while open.
dir'ltr' | 'rtl''ltr'Reading direction, affecting submenu placement and arrow keys.

DropdownMenuContent

PropTypeDefaultDescription
align'start' | 'center' | 'end''center'Alignment of the content against the trigger.
side'top' | 'right' | 'bottom' | 'left''bottom'Preferred side of the trigger to render against.
sideOffsetnumber4Distance in pixels between the content and the trigger. The package raises the Radix default of 0 to 4.
alignOffsetnumber0Offset along the alignment axis, in pixels.
loopbooleanfalseWhether keyboard navigation wraps around from the last item to the first.
classNamestringAdditional Tailwind classes — commonly a width such as w-56.

DropdownMenuItem

PropTypeDefaultDescription
variant'default' | 'destructive''default'Styles the item — destructive paints the label and icon with the destructive ink token; the focus row tints with the destructive fill at bg-destructive/10 (bg-destructive/20 in dark).
insetbooleanfalseAdds left padding to align with items that have a leading icon or indicator.
disabledbooleanfalseDisables the item, removing pointer events and dimming it.
onSelect(event: Event) => voidCalled when the item is selected via mouse or keyboard.
textValuestringText used for typeahead matching when the item contains more than plain text.
asChildbooleanfalseMerges the item props onto its single child — use it to render a link as a menu item.

Accessibility

  • Built on Radix UI, the menu follows the WAI-ARIA menu button pattern with the correct role and aria-* wiring applied automatically.
  • Fully keyboard operable — open with Enter, Space, or arrow keys, move with arrows, activate with Enter, and dismiss with Esc.
  • Typeahead lets users jump to an item by typing its label; submenus open with and close with .
  • Focus is trapped while the menu is open and returns to the trigger on close; checkbox and radio items expose their checked state to assistive technology.
  • variant="destructive" paints the label in the destructive ink step, not the destructive fill. The fill is a button background pinned to one value across both themes and only reaches 3.53:1 as text on the popover surface — worse still on focus, where the row tints to bg-destructive/20 (2.98:1). The ink step clears both themes and both backgrounds: 7.15:1 / 6.18:1 in light at rest and on focus, 6.42:1 / 5.42:1 in dark.
  • Keyboard shortcut glyphs are decorative. KeyboardHint is aria-hidden for that reason — symbols such as ⌘⇧↵ are meaningless read aloud, so put the combination in the item's own accessible name when it matters.