Experience System
Components

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-system

In 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
        └── DropdownMenuSubContent

DropdownMenuTrigger 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.

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.

PropTypeDefault
defaultOpenbooleanNo default value
openbooleanNo default value
onOpenChangefunctionNo default value
modalbooleantrue
direnumFrom ThemeProvider

DropdownMenuTrigger — the control that toggles the menu. Use asChild to merge props into a Button or other focusable element.

PropTypeDefault
asChildbooleanfalse
disabledbooleanfalse
Data attributeValues
[data-state]"open" | "closed"

DropdownMenuPortal — portals children into the document. In this experience system, container defaults to the element from useContainerElement() (ThemeProvider) instead of document.body.

PropTypeDefault
forceMountbooleanNo default value
containerHTMLElementFrom ThemeProvider (see above)

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).

PropTypeDefault
asChildbooleanfalse
loopbooleanfalse
onCloseAutoFocusfunctionNo default value
onEscapeKeyDownfunctionNo default value
onPointerDownOutsidefunctionNo default value
onFocusOutsidefunctionNo default value
onInteractOutsidefunctionNo default value
forceMountbooleanNo default value
sideenum"bottom"
sideOffsetnumber0
alignenum"center"
alignOffsetnumber0
avoidCollisionsbooleantrue
collisionBoundaryBoundary[]
collisionPaddingnumber | Padding0
stickyenum"partial"
hideWhenDetachedbooleanfalse
Data attributeValues
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
CSS variableDescription
--radix-dropdown-menu-content-transform-originThe transform-origin computed from the content and trigger positions
--radix-dropdown-menu-content-available-widthThe remaining width between the trigger and the boundary edge
--radix-dropdown-menu-content-available-heightThe remaining height between the trigger and the boundary edge
--radix-dropdown-menu-trigger-widthThe width of the trigger
--radix-dropdown-menu-trigger-heightThe height of the trigger

DropdownMenuItem — a selectable command in the menu.

PropTypeDefault
asChildbooleanfalse
disabledbooleanNo default value
onSelectfunctionNo default value
textValuestringNo default value
insetbooleanNo default value (experience system: aligns text with icon rows)
colorenum"neutral" ("neutral" | "error" — use error for irreversible actions)
Data attributeValues
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-color]"neutral" | "error"

DropdownMenuGroup — groups multiple items for semantics and optional labeling.

PropTypeDefault
asChildbooleanfalse

DropdownMenuLabel — a non-focusable label for a section. Optional inset aligns with inset items.

PropTypeDefault
asChildbooleanfalse
insetbooleanNo default value (experience system)

DropdownMenuCheckboxItem — a toggle row backed by a checkbox. Uses a Check indicator internally.

PropTypeDefault
asChildbooleanfalse
checkedboolean | 'indeterminate'No default value
onCheckedChangefunctionNo default value
disabledbooleanNo default value
onSelectfunctionNo default value
textValuestringNo default value
Data attributeValues
[data-state]"checked" | "unchecked" | "indeterminate"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

DropdownMenuRadioGroup — groups DropdownMenuRadioItem components.

PropTypeDefault
asChildbooleanfalse
valuestringNo default value
onValueChangefunctionNo default value

DropdownMenuRadioItem — a single choice in a radio group. Indicator uses ShapeBulletCircleFill.

PropTypeDefault
asChildbooleanfalse
valuestringNo default value (required)
disabledbooleanNo default value
onSelectfunctionNo default value
textValuestringNo default value
Data attributeValues
[data-state]"checked" | "unchecked"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

DropdownMenuSeparator — a visual divider between groups.

PropTypeDefault
asChildbooleanfalse

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 — contains all parts of a submenu.

PropTypeDefault
defaultOpenbooleanNo default value
openbooleanNo default value
onOpenChangefunctionNo default value

DropdownMenuSubTrigger — opens a nested menu. Includes a trailing ArrowChevronRight icon.

PropTypeDefault
asChildbooleanfalse
disabledbooleanNo default value
textValuestringNo default value
insetbooleanNo default value (experience system)
Data attributeValues
[data-state]"open" | "closed"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

DropdownMenuSubContent — the panel for a submenu. Render inside DropdownMenuSub.

PropTypeDefault
asChildbooleanfalse
loopbooleanfalse
onEscapeKeyDownfunctionNo default value
onPointerDownOutsidefunctionNo default value
onFocusOutsidefunctionNo default value
onInteractOutsidefunctionNo default value
forceMountbooleanNo default value
sideOffsetnumber0
alignOffsetnumber0
avoidCollisionsbooleantrue
collisionBoundaryBoundary[]
collisionPaddingnumber | Padding0
arrowPaddingnumber0
stickyenum"partial"
hideWhenDetachedbooleanfalse
Data attributeValues
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
CSS variableDescription
--radix-dropdown-menu-content-transform-originThe 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

KeyDescription
Space / EnterOpens the menu from the trigger, or activates the focused item.
ArrowDown / ArrowUpMoves focus between items when the menu is open.
ArrowRight / ArrowLeftWhen focus is on DropdownMenuSubTrigger, opens or closes the submenu (respects reading direction).
EscCloses 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.