Dropdown Menu
A menu opened from a button or control, for actions and settings without leaving the current view.
Installation
The component is exported from @by/experience-system. Add the package with your package manager:
pnpm add @by/experience-systemIn this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.
Composition
Use the following composition to build a DropdownMenu:
DropdownMenu
├── DropdownMenuTrigger
└── DropdownMenuContent
├── DropdownMenuLabel (optional)
├── DropdownMenuItem / CheckboxItem / RadioGroup / …
├── DropdownMenuSeparator (optional)
└── DropdownMenuSub (optional)
├── DropdownMenuSubTrigger
└── DropdownMenuSubContentDropdownMenuTrigger is usually a Button (with asChild). Commands and groups live in DropdownMenuContent. Use DropdownMenuSub for nested menus. Behavior follows Radix UI Dropdown Menu.
Usage
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
Button,
} from '@by/experience-system';DropdownMenu and its parts are client components ('use client'). Use them inside a Client Component or a dynamic import when using the Next.js App Router.
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open menu</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>Pay attention:
Apply rtl:scale-x-[-1] to icons that imply direction (for example undo, redo, reload) so they mirror correctly in RTL layouts.
When to use
- When secondary actions should stay one click away but not compete with primary UI on the page.
- When grouping related commands (edit, share, delete) or settings toggles (checkboxes, radio groups) in a compact surface.
When not to use
- When the only action is primary navigation—prefer visible links or tabs.
- When the list grows so long that scanning becomes difficult—consider a dialog, drawer, or dedicated settings page.
Examples
The live preview at the top of this page uses dropdown-menu-usage (outline button trigger + two items).
Complex content
Items with leading icons, inset alignment for icon-less rows, shortcuts, a nested submenu (including an error-styled row via color="error"), checkbox items, and a radio group with an inset label.
API Reference
Subsection titles name @by/experience-system exports and the Radix part they wrap. Pieces map to Radix UI Dropdown Menu primitives. Styling, density-aware spacing, and icons are provided by @by/experience-system and @by/icons. The menu follows the Menu WAI-ARIA design pattern and uses roving tabindex to move focus among items.
DropdownMenu
Root. DropdownMenu contains all the parts of a dropdown menu. Exported as DropdownMenu; forwards DropdownMenu.Root props from radix-ui. dir defaults from ThemeProvider and can be overridden via props.
| Prop | Type | Default |
|---|---|---|
defaultOpen | boolean | No default value |
open | boolean | No default value |
onOpenChange | function | No default value |
modal | boolean | true |
dir | enum | From ThemeProvider |
DropdownMenuTrigger
DropdownMenuTrigger — the control that toggles the menu. Use asChild to merge props into a Button or other focusable element.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
disabled | boolean | false |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
DropdownMenuPortal
DropdownMenuPortal — portals children into the document. In this experience system, container defaults to the element from useContainerElement() (ThemeProvider) instead of document.body.
| Prop | Type | Default |
|---|---|---|
forceMount | boolean | No default value |
container | HTMLElement | From ThemeProvider (see above) |
DropdownMenuContent
DropdownMenuContent — the surface that opens from the trigger. This export wraps Portal with the Experience System container so you typically render DropdownMenuContent directly (without a separate DropdownMenuPortal for the main menu).
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
loop | boolean | false |
onCloseAutoFocus | function | No default value |
onEscapeKeyDown | function | No default value |
onPointerDownOutside | function | No default value |
onFocusOutside | function | No default value |
onInteractOutside | function | No default value |
forceMount | boolean | No default value |
side | enum | "bottom" |
sideOffset | number | 0 |
align | enum | "center" |
alignOffset | number | 0 |
avoidCollisions | boolean | true |
collisionBoundary | Boundary | [] |
collisionPadding | number | Padding | 0 |
sticky | enum | "partial" |
hideWhenDetached | boolean | false |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
| CSS variable | Description |
|---|---|
--radix-dropdown-menu-content-transform-origin | The transform-origin computed from the content and trigger positions |
--radix-dropdown-menu-content-available-width | The remaining width between the trigger and the boundary edge |
--radix-dropdown-menu-content-available-height | The remaining height between the trigger and the boundary edge |
--radix-dropdown-menu-trigger-width | The width of the trigger |
--radix-dropdown-menu-trigger-height | The height of the trigger |
DropdownMenuItem
DropdownMenuItem — a selectable command in the menu.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
disabled | boolean | No default value |
onSelect | function | No default value |
textValue | string | No default value |
inset | boolean | No default value (experience system: aligns text with icon rows) |
color | enum | "neutral" ("neutral" | "error" — use error for irreversible actions) |
| Data attribute | Values |
|---|---|
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
[data-color] | "neutral" | "error" |
DropdownMenuGroup
DropdownMenuGroup — groups multiple items for semantics and optional labeling.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
DropdownMenuLabel
DropdownMenuLabel — a non-focusable label for a section. Optional inset aligns with inset items.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
inset | boolean | No default value (experience system) |
DropdownMenuCheckboxItem
DropdownMenuCheckboxItem — a toggle row backed by a checkbox. Uses a Check indicator internally.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
checked | boolean | 'indeterminate' | No default value |
onCheckedChange | function | No default value |
disabled | boolean | No default value |
onSelect | function | No default value |
textValue | string | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "checked" | "unchecked" | "indeterminate" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
DropdownMenuRadioGroup
DropdownMenuRadioGroup — groups DropdownMenuRadioItem components.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
value | string | No default value |
onValueChange | function | No default value |
DropdownMenuRadioItem
DropdownMenuRadioItem — a single choice in a radio group. Indicator uses ShapeBulletCircleFill.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
value | string | No default value (required) |
disabled | boolean | No default value |
onSelect | function | No default value |
textValue | string | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "checked" | "unchecked" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
DropdownMenuSeparator
DropdownMenuSeparator — a visual divider between groups.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
DropdownMenuShortcut
DropdownMenuShortcut — Experience System helper: a span for keyboard hints, aligned to the trailing edge when placed inside an item. Not a Radix primitive part.
DropdownMenuSub
DropdownMenuSub — contains all parts of a submenu.
| Prop | Type | Default |
|---|---|---|
defaultOpen | boolean | No default value |
open | boolean | No default value |
onOpenChange | function | No default value |
DropdownMenuSubTrigger
DropdownMenuSubTrigger — opens a nested menu. Includes a trailing ArrowChevronRight icon.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
disabled | boolean | No default value |
textValue | string | No default value |
inset | boolean | No default value (experience system) |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
DropdownMenuSubContent
DropdownMenuSubContent — the panel for a submenu. Render inside DropdownMenuSub.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
loop | boolean | false |
onEscapeKeyDown | function | No default value |
onPointerDownOutside | function | No default value |
onFocusOutside | function | No default value |
onInteractOutside | function | No default value |
forceMount | boolean | No default value |
sideOffset | number | 0 |
alignOffset | number | 0 |
avoidCollisions | boolean | true |
collisionBoundary | Boundary | [] |
collisionPadding | number | Padding | 0 |
arrowPadding | number | 0 |
sticky | enum | "partial" |
hideWhenDetached | boolean | false |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
| CSS variable | Description |
|---|---|
--radix-dropdown-menu-content-transform-origin | The transform-origin computed for submenu content |
@by/experience-system does not export DropdownMenuArrow; DropdownMenuSubTrigger includes a trailing chevron instead. For Radix’s optional arrow API, see the upstream docs.
Accessibility
Uses roving tabindex among items. Prefer DropdownMenuLabel (optionally inset) for section titles, and give icon-only triggers an accessible name (aria-label on the button).
Keyboard interactions
| Key | Description |
|---|---|
Space / Enter | Opens the menu from the trigger, or activates the focused item. |
ArrowDown / ArrowUp | Moves focus between items when the menu is open. |
ArrowRight / ArrowLeft | When focus is on DropdownMenuSubTrigger, opens or closes the submenu (respects reading direction). |
Esc | Closes the menu. |
For the full primitive reference and upstream updates, see Radix UI — Dropdown Menu.
Implementation: packages/experience-system/src/components/DropdownMenu/DropdownMenu.tsx. Codegen-oriented rules for AI agents: packages/experience-system/src/components/DropdownMenu/DropdownMenu.instructions.md.