Light

Dropdown Menu

Displays a menu to the user — such as a set of actions or functions — triggered by a button.

Spec · from metadata

When to use

  • Action menus triggered by a button (e.g., 'More actions' or kebab menu)
  • Option selection with checkbox or radio items
  • Nested menu hierarchies via sub-menus
  • Any list of actions that should appear on click

When not to use

  • Right-click context menus -- use ContextMenu instead
  • App-level persistent menu bars -- use Menubar instead
  • Rich interactive content (forms, inputs) -- use Popover instead
  • Navigation links -- use NavigationMenu instead
  • Simple toggle actions -- use a Button or Toggle

Variants

PropValuesDefaultDescription
variant (on DropdownMenuItem)defaultdestructivedefaultControls item styling. 'destructive' applies red text and destructive focus styles.

Anti-patterns

Avoid<Popover>
  <PopoverTrigger asChild>
    <Button>Actions</Button>
  </PopoverTrigger>
  <PopoverContent>
    <button onClick={handleEdit}>Edit</button>
    <button onClick={handleDelete}>Delete</button>
  </PopoverContent>
</Popover>
Prefer<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button>Actions</Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem onSelect={handleEdit}>Edit</DropdownMenuItem>
    <DropdownMenuItem variant="destructive" onSelect={handleDelete}>
      Delete
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

DropdownMenu provides proper menu ARIA semantics (role='menu', role='menuitem'), arrow key navigation, and typeahead search. Popover has none of these.

Avoid<DropdownMenuItem>
  Delete <TrashIcon />
</DropdownMenuItem>
Prefer<DropdownMenuItem variant="destructive">
  <TrashIcon /> Delete
</DropdownMenuItem>

Icons go before the label. Use variant='destructive' for delete actions to get proper red styling and accessible indication.

Accessibility

  • Required ARIAaria-labelledby (via trigger)
  • Screen readerAnnounced as a menu. Items are announced as menuitems. Checkbox and radio items announce their checked state. Disabled items announce as disabled.
  • ContrastUses bg-popover/text-popover-foreground. Focus state uses bg-accent/text-accent-foreground. Destructive items use text-destructive.

Token bindings

TokenCategoryUsage
bg-popovercolorMenu background
text-popover-foregroundcolorMenu text
bg-accentcolorFocused item background
text-accent-foregroundcolorFocused item text
text-destructivecolorDestructive item text
text-muted-foregroundcolorIcons and shortcuts
bg-bordercolorSeparator
shadow-mdshadowMenu elevation
rounded-mdradiusMenu border radius
rounded-smradiusItem border radius

Import

import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioItem,
  DropdownMenuRadioGroup,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubTrigger,
  DropdownMenuSubContent,
} from "@timelycare/helix-ui"

Props

interface DropdownMenuItemProps {
  disabled?: boolean
  onSelect?: () => void
  className?: string
  children: React.ReactNode
}

interface DropdownMenuCheckboxItemProps extends DropdownMenuItemProps {
  checked?: boolean
  onCheckedChange?: (checked: boolean) => void
}

Design Tokens

Container

PropertyTokenValue
Backgroundbg-popoverwhite
Borderborder1px solid border color
Border radiusrounded-md8px
Paddingp-14px
Shadowshadow-mdMulti-layer drop shadow
Min width224px

Item Sizing

PropertyTokenValue
Height32px
Padding (default)px-2 py-1.58px horizontal, 6px vertical
Padding (with indicator)pl-8 pr-2 py-1.532px left, 8px right, 6px vertical
Border radiusrounded-sm6px
Gapgap-28px

Typography

ElementSizeColorWeight
Item texttext-sm (14px)text-popover-foregroundnormal
Shortcut texttext-xs (12px)opacity-60normal
Labeltext-sm (14px)text-popover-foregroundsemibold

Icons & Indicators

PropertyTokenValue
Icon sizesize-416px × 16px
Indicator positionleft-28px from left edge
Icon color (default)text-muted-foreground#646565
Check indicator colortext-foreground#09090b
SubTrigger chevronsize-4ChevronRight icon

Separator

PropertyTokenValue
Heighth-px1px
Colorborder#e4e4e7

Item Variants

VariantUse ForTailwind
defaultStandard actionstext-popover-foreground
checkboxToggleable settingstext-popover-foreground + checkmark indicator
radioSingle-select optionstext-popover-foreground + dot indicator
iconActions with leading iconsIcon (text-muted-foreground) + text

Choosing a Variant

  • Use default for actions (Profile, Logout, Delete)
  • Use checkbox for independent toggles (Show Status Bar)
  • Use radio for mutually exclusive choices (Panel Position)
  • Use icon for items with contextual icons

States

StateImplementation
Defaulttext-popover-foreground
Hoverbg-accent text-accent-foreground (automatic)
Focusbg-accent text-accent-foreground
Disabledopacity-50 pointer-events-none
Errortext-destructive (icon variant only)

Common Patterns

Basic Menu

<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="outline">Open</Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuLabel>My Account</DropdownMenuLabel>
    <DropdownMenuSeparator />
    <DropdownMenuItem>
      Profile <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
    </DropdownMenuItem>
    <DropdownMenuItem>Billing</DropdownMenuItem>
    <DropdownMenuItem>Settings</DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem>Logout</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

With Submenu

<DropdownMenuSub>
  <DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
  <DropdownMenuSubContent>
    <DropdownMenuItem>Email</DropdownMenuItem>
    <DropdownMenuItem>Message</DropdownMenuItem>
  </DropdownMenuSubContent>
</DropdownMenuSub>

Checkbox & Radio Items

<DropdownMenuCheckboxItem checked={showPanel} onCheckedChange={setShowPanel}>
  Status Bar
</DropdownMenuCheckboxItem>

<DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
  <DropdownMenuLabel>Panel Position</DropdownMenuLabel>
  <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
  <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>

Action Menu with Icons

Row-level action menus with icon indicators and destructive actions.

<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="ghost" size="icon">
      <MoreHorizontal className="size-4" />
    </Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent align="end">
    <DropdownMenuItem>
      <Pencil className="size-4 mr-2" />
      Edit
    </DropdownMenuItem>
    <DropdownMenuItem>
      <Copy className="size-4 mr-2" />
      Duplicate
    </DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem className="text-destructive">
      <Trash2 className="size-4 mr-2" />
      Delete
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

Icon Spacing

  • Icon to text: mr-2 (8px)
  • Icon size: size-4 (16px)
  • Destructive items: text-destructive on the item

Avatar Trigger

<DropdownMenuTrigger asChild>
  <Button variant="ghost" className="relative h-10 w-10 rounded-full">
    <Avatar><AvatarImage src="/user.jpg" /></Avatar>
  </Button>
</DropdownMenuTrigger>

Accessibility

  • Built on Radix DropdownMenu — handles keyboard and focus automatically
  • Keyboard: Enter/Space to open, Arrow keys to navigate, Enter to select, Escape to close
  • Screen reader: announces menu role, item count, and selection
  • Typeahead search supported within menu items

Gotchas

ProblemSolution
Menu not openingAdd asChild to DropdownMenuTrigger
Shortcut not alignedUse DropdownMenuShortcut (has ml-auto)
Submenu not showingWrap in DropdownMenuSub with SubTrigger + SubContent
Radio items not groupedWrap in DropdownMenuRadioGroup

See Also


Last updated: February 9, 2026