Skip to main content

DropdownMenu

  • Component overview: Dropdown menu that displays a list of actions below a trigger element. Supports Default/Danger semantic variants, icon slots, and cascading sub-menus.
  • Size baseline: Content min-width 160px, padding-y 8px, radius 5px; Item padding 16px × 8px, font 14px/20px.
  • Implementation note: Standard usage via declarative items config, including cascading menus through subItems. Only use the composition API for advanced scenarios such as CheckboxItem / RadioItem. ui/dropdown-menu retains structural skeleton and animations; the design layer takes over visuals via unstyledVisual.
  • Figma spec

Basic Usage

Configure menu items declaratively via items; pass the trigger element as children.

Result
Loading...
Live Editor
render(
  <DropdownMenu
    items={[{ label: 'Profile' }, { label: 'Settings' }, { type: 'separator' }, { label: 'Log out' }]}
  >
    <Button variant="secondary">Open Menu</Button>
  </DropdownMenu>,
)

Variants

Use variant to distinguish action semantics.

Result
Loading...
Live Editor
render(
  <DropdownMenu
    items={[
      { label: 'Edit' },
      { label: 'Duplicate' },
      { type: 'separator' },
      { label: 'Delete', variant: 'danger' },
    ]}
  >
    <Button variant="secondary">Actions</Button>
  </DropdownMenu>,
)

States

Result
Loading...
Live Editor
render(
  <DropdownMenu
    items={[
      { label: 'Normal item' },
      { label: 'Disabled item', disabled: true },
      { type: 'separator' },
      { label: 'Danger item', variant: 'danger' },
      { label: 'Disabled danger', variant: 'danger', disabled: true },
    ]}
  >
    <Button variant="secondary">States</Button>
  </DropdownMenu>,
)

With Icons

Add a leading icon to menu items via the icon field.

Result
Loading...
Live Editor
const EditIcon = () => (
  <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M10.5 1.75L12.25 3.5L10.5 5.25M1.75 12.25H3.5L10.5 5.25L8.75 3.5L1.75 10.5V12.25Z" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round"/>
  </svg>
)

const TrashIcon = () => (
  <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M1.75 3.5H12.25M5.25 6.125V10.375M8.75 6.125V10.375M2.625 3.5L3.5 11.375C3.5 11.8582 3.89175 12.25 4.375 12.25H9.625C10.1082 12.25 10.5 11.8582 10.5 11.375L11.375 3.5M4.375 3.5V2.625C4.375 2.14175 4.76675 1.75 5.25 1.75H8.75C9.23325 1.75 9.625 2.14175 9.625 2.625V3.5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round"/>
  </svg>
)

const ArrowIcon = ({ direction }) => {
  const rotationMap = {
    up: 'rotate(-180deg)',
    right: 'rotate(-90deg)',
    down: 'rotate(0deg)',
    left: 'rotate(90deg)',
  }

  return (
    <svg
      width="14"
      height="14"
      viewBox="0 0 14 14"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      style={{ transform: rotationMap[direction] }}
    >
      <path
        d="M3.5 5.25L7 8.75L10.5 5.25"
        stroke="currentColor"
        strokeWidth="1.2"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  )
}

render(
  <DropdownMenu
    items={[
      { label: 'Edit', icon: <EditIcon /> },
      { label: 'Arrow Up', icon: <ArrowIcon direction="up" /> },
      { label: 'Arrow Right', icon: <ArrowIcon direction="right" /> },
      { label: 'Arrow Down', icon: <ArrowIcon direction="down" /> },
      { label: 'Arrow Left', icon: <ArrowIcon direction="left" /> },
      { type: 'separator' },
      { label: 'Delete', variant: 'danger', icon: <TrashIcon /> },
    ]}
  >
    <Button variant="secondary">With Icons</Button>
  </DropdownMenu>,
)

Label Groups

Result
Loading...
Live Editor
render(
  <DropdownMenu
    items={[
      { type: 'label', label: 'My Account' },
      { label: 'Profile' },
      { label: 'Settings' },
      { type: 'separator' },
      { type: 'label', label: 'Team' },
      { label: 'Team Settings' },
      { label: 'Invite Members' },
    ]}
  >
    <Button variant="secondary">Grouped Menu</Button>
  </DropdownMenu>,
)

Cascading Submenu

Result
Loading...
Live Editor
render(
  <DropdownMenu
    items={[
      { label: 'New File' },
      {
        label: 'More Options',
        subItems: [{ label: 'Import' }, { label: 'Export' }],
      },
      { type: 'separator' },
      { label: 'Delete', variant: 'danger' },
    ]}
  >
    <Button variant="secondary">Cascading Menu</Button>
  </DropdownMenu>,
)

Size Spec

Content Container

PropertyTokenValue
Min width160px
Vertical paddingSpacing_88px
Border radiusRadius_55px
ShadowEffects/Shadow/Default0 0 32px rgba(0,0,0,0.1)

Item

PropertyTokenValue
Horizontal paddingSpacing_1616px
Vertical paddingSpacing_88px
Font sizeFont-Size/Body14px
Line heightLine-Height/Body22px
Icon container20×20px

Color Tokens

StateText ColorHover Background
DefaultLabels/Secondary#3d3d3dGrays/Gray-1#ebebeb
DangerLabels/Error#cc3325Grays/Gray-1#ebebeb
DisabledLabels/Disabled#a3a3a3

Props

PropTypeDefaultDescription
childrenReactNode-Trigger element
itemsDropdownMenuItemEntry[]-Declarative menu item config
asChildbooleantrueWhether to render children as the Trigger element
openboolean-Controlled open state
onOpenChange(open: boolean) => void-Open state change callback
contentClassNamestring-Custom className for Content
side'top' | 'right' | 'bottom' | 'left''bottom'Popup direction
align'start' | 'center' | 'end''start'Alignment
Kindtype valueRequired fieldsDescription
Menu item'item' (can be omitted)labelSupports variant, icon, disabled, onSelect, subItems, itemProps
Separator'separator'-Renders as DropdownMenuSeparator
Group label'label'labelRenders as DropdownMenuLabel
PropTypeDefaultDescription
childrenReactNode-Item content
variant'default' | 'danger''default'Semantic variant
iconReactNode-Leading icon
disabledbooleanfalseDisabled state
onSelect(event: Event) => void-Select callback
classNamestring-Custom className

Advanced Usage

Prefer items / subItems by default. Only when you need CheckboxItem, RadioItem, or fully custom content composition should you switch to DropdownMenu + DropdownMenuTrigger + DropdownMenuContent.