ProductBrand
Join us
Foundations
  • Introduction
  • Colors
  • Typography
  • Icons
  • Illustrations
  • Logos
  • Shadows
Components
  • Accordion
  • Alert Dialog
  • Avatar
  • Badge
  • Button
  • Calendar
  • Card
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Context Menu
  • Date Picker
  • Dialog
  • Drawer
  • Dropdown Menu
  • Fluid Avatar
  • Hover Scroll Text
  • Input
  • Kbd
  • Popover
  • Progress Bar
  • Radio Group
  • Scroll Area
  • Select
  • Selectors
  • Separator
  • Separator Dot
  • Shimmer Text
  • Skeleton
  • Slider
  • Switch
  • Tabs
  • Textarea
  • Toast
  • Tooltip
Compositions
  • Empty State
  • File Attachment
  • Floating Bar
  • Input Banner
  • Markdown Editor
  • Media Controls
  • Product Feature Banner
  • Panels
  • Sidebar
  • Reasoning Trace
  • Pagination
  • Data Table
  • Breadcrumb
  • Thread Outline
Inline
  • Overview
  • Stat Tile
  • Metric Row
  • Comparison Table
  • Chart Card
  • Trend Tile
  • Cite
  • Custom
  • Agent skill

Dropdown Menu

Dropdown menus display a list of actions or options that a user can choose from, triggered by a button. Built on Base UI Menu with the same parts shape as shadcn's dropdown-menu. Every item is a horizontal row with an optional left slot, label, and optional right slot.

For composite picker patterns (model selector, datasource selector, settings toggles), see Selectors.

Examples

Account menu (icons + radio submenu)

Row actions (submenus, destructive)

Items with tooltips (tooltip prop)

Single select (right-slot checkmark)

Multi select (left-slot checkbox)

Searchable, single select

Searchable, multi select

Radio items

Anatomy

Every row has the same horizontal slot layout: an optional left slot, the label, and an optional right slot.

VariantLeft slotRight slot
DropdownMenuItemprefix (icon)suffix / shortcut / selected checkmark
DropdownMenuCheckboxItemCheckbox (auto)Optional checkmark when checked (off by default in pickers)
DropdownMenuRadioItem-Checkmark when selected
DropdownMenuSubTrigger-Chevron (auto)

Separators bleed edge-to-edge of the popup (-mx-1), so use them to split groups visually.

The trigger receives a data-popup-open attribute while the menu is open. Wire your own active styles to it (the demos pass className="data-[popup-open]:bg-tertiary" through Button asChild, matching the outline variant's hover background) so users can tell which trigger owns the visible popover.

Usage

Basic with shortcuts

tsx
<DropdownMenu>
  <DropdownMenuTrigger>
    <button>Open</button>
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuLabel>My Account</DropdownMenuLabel>
    <DropdownMenuItem>
      Profile
      <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
    </DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem>Log out</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

Single select (right-slot checkmark)

Render plain DropdownMenuItems and pass selected to draw a checkmark on the right. The menu closes on select by default.

tsx
const [value, setValue] = useState("Option 1");

<DropdownMenuContent>
  <DropdownMenuLabel>Group title</DropdownMenuLabel>
  {options.map((option) => (
    <DropdownMenuItem key={option} selected={value === option} onSelect={() => setValue(option)}>
      {option}
    </DropdownMenuItem>
  ))}
</DropdownMenuContent>;

Multi select (left-slot checkbox)

DropdownMenuCheckboxItem renders a checkbox visual on the left. Pass closeOnClick={false} so the menu stays open while toggling, and showSelectedIndicator={false} so checked rows don't double up with a right-slot checkmark, since the left checkbox already communicates state.

tsx
<DropdownMenuContent>
  <DropdownMenuLabel>Labels</DropdownMenuLabel>
  {labels.map((label) => (
    <DropdownMenuCheckboxItem
      key={label}
      checked={state[label]}
      onCheckedChange={(checked) => setState((p) => ({ ...p, [label]: checked === true }))}
      closeOnClick={false}
      showSelectedIndicator={false}
    >
      {label}
    </DropdownMenuCheckboxItem>
  ))}
</DropdownMenuContent>

The prop pairing is intentional: single-select uses the right-slot checkmark via DropdownMenuItem selected, multi-select uses the left-slot checkbox via DropdownMenuCheckboxItem, never both. If you really need extra emphasis on the checked row, set showSelectedIndicator to true to bring the right-slot checkmark back.

Searchable (Linear style)

Drop a DropdownMenuSearch as the first child of DropdownMenuContent. It autofocuses on open and renders a full-width divider below. The input is just a plain text input by default. Pass prefix / suffix to add an icon, clear button, or hint. Filtering logic is yours; the demos use Fuse.js with threshold: 0.4 and a memoized index, which tolerates typos and partial matches without re-allocating on each keystroke.

tsx
import { IconMagnifyingGlass } from "@rogo-technologies/ui/icons";

<DropdownMenuSearch
  value={query}
  onChange={(e) => setQuery(e.target.value)}
  prefix={<IconMagnifyingGlass className="text-tertiary size-4" />}
/>;
tsx
const [query, setQuery] = useState("");
const filtered = options.filter((o) => o.toLowerCase().includes(query.toLowerCase()));

<DropdownMenuContent className="min-w-72">
  <DropdownMenuSearch
    value={query}
    onChange={(e) => setQuery(e.target.value)}
    placeholder="Search metrics..."
  />
  {filtered.length === 0 ? (
    <div className="text-tertiary px-2 py-6 text-center text-sm">No results</div>
  ) : (
    filtered.map((option) => (
      <DropdownMenuItem key={option} selected={value === option} onSelect={() => setValue(option)}>
        {option}
      </DropdownMenuItem>
    ))
  )}
</DropdownMenuContent>;

Arrow keys move focus through items even while the input has focus. Pass autoFocusOnMount={false} if you don't want the input to grab focus on open.

Radio items

Single-select via a DropdownMenuRadioGroup. Selected items show a checkmark in the right slot.

tsx
<DropdownMenuRadioGroup value={sort} onValueChange={setSort}>
  <DropdownMenuRadioItem value="relevance">Relevance</DropdownMenuRadioItem>
  <DropdownMenuRadioItem value="date">Date</DropdownMenuRadioItem>
  <DropdownMenuRadioItem value="name">Name</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>

Tooltips on items (tooltip prop)

Pass tooltip on any DropdownMenuItem to explain what the row does on hover/focus, with no need to wrap anything yourself. DropdownMenuContent already provides a TooltipProvider internally, so the first tooltip waits ~400ms, and once it opens, hovering neighbours shows their tooltips instantly.

tsx
<DropdownMenuItem
  prefix={<IconEyeOpen className="text-tertiary" />}
  tooltip="Can open and read but cannot make changes."
>
  Viewer
</DropdownMenuItem>

tooltip accepts a string or any ReactNode, so you can pass richer content when needed:

tsx
<DropdownMenuItem
  prefix={<IconSettingsGear2 className="text-tertiary" />}
  tooltip={
    <div className="flex flex-col gap-1">
      <span className="font-medium">Admin</span>
      <span className="text-xs opacity-80">
        Full control: share, manage members, change settings, delete.
      </span>
    </div>
  }
>
  Admin
</DropdownMenuItem>

By default the tooltip appears on the right so it clears the menu. Override with tooltipSide="top" | "right" | "bottom" | "left" if the menu sits against the viewport edge and the right side gets clipped. Base UI's collision detection will also flip automatically if needed.

Arbitrary content in prefix / suffix

prefix and suffix on DropdownMenuItem are typed React.ReactNode, so any element works, including toggles, badges, avatars, kbd hints, and count pills. Pair with closeOnClick={false} so toggling the control doesn't dismiss the menu, and stop event propagation inside the control if its click shouldn't also fire onSelect on the row.

tsx
<DropdownMenuItem
  closeOnClick={false}
  onSelect={() => setEnabled(!enabled)}
  prefix={<IconBell className="text-tertiary" />}
  suffix={<Switch checked={enabled} onCheckedChange={setEnabled} label="Push notifications" />}
>
  Push notifications
</DropdownMenuItem>

DropdownMenuCheckboxItem and DropdownMenuRadioItem manage their own slots. For non-default controls (toggles instead of checkboxes, custom selected indicators, etc.) use a plain DropdownMenuItem and put your control in suffix.

Items with a description (two-line layout)

When the option list is small and the differences between items matter (model pickers, plan selectors, integration choices), put the description inline instead of behind a tooltip. Pass the provider/leading icon via prefix and a vertical flex column as children, so the row stays as one DropdownMenuItem and selected still drives the right-slot checkmark.

tsx
<DropdownMenuItem
  selected={value === model.id}
  onSelect={() => setValue(model.id)}
  prefix={<ProviderIcon />}
>
  <div className="flex min-w-0 flex-col leading-tight">
    <span className="text-primary truncate">{model.name}</span>
    <span className="text-tertiary truncate text-xs">{model.description}</span>
  </div>
</DropdownMenuItem>

leading-tight pulls the two lines closer than the row's default; min-w-0 + truncate on each line stops a long description from blowing out the menu width.

Submenus

Submenus open on hover and on keyboard highlight (after a 100 ms delay) so users can peek inside without committing. Focus only moves into the submenu when the user presses Right Arrow or Enter. Pass openOnHover={false} on the sub-trigger to disable hover-open for nested cases where the eager reveal feels noisy.

tsx
<DropdownMenuSub>
  <DropdownMenuSubTrigger>
    <IconShareOs className="text-tertiary" />
    <span>Share</span>
  </DropdownMenuSubTrigger>
  <DropdownMenuSubContent>
    <DropdownMenuItem>
      <IconChainLink1 className="text-tertiary" />
      <span>Copy link</span>
    </DropdownMenuItem>
    <DropdownMenuItem>
      <IconEmail1 className="text-tertiary" />
      <span>Email</span>
    </DropdownMenuItem>
  </DropdownMenuSubContent>
</DropdownMenuSub>

Destructive items

tsx
<DropdownMenuItem actionType="destructive">Delete</DropdownMenuItem>

Trigger with asChild

Compose the trigger with a real Button. The data-popup-open attribute is forwarded to the rendered element, so you can darken the trigger while the menu is open by passing a Tailwind selector through className:

tsx
import { Button } from "@rogo-technologies/ui/button";
import { IconChevronDownSmall } from "@rogo-technologies/ui/icons";

<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button
      variant="outline"
      suffix={<IconChevronDownSmall />}
      className="data-[popup-open]:bg-tertiary"
    >
      Options
    </Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>Edit</DropdownMenuItem>
    <DropdownMenuItem>Delete</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>;

Positioning

tsx
<DropdownMenuContent side="right" align="start" sideOffset={8}>…</DropdownMenuContent>
<DropdownMenuContent side="top" align="center">…</DropdownMenuContent>

Accessibility

  • Menu uses role="menu" with role="menuitem" children
  • Keyboard: ↑/↓ to move, Enter/Space to select, Esc to close
  • Submenus open with → and close with ←
  • Focus is trapped while open
  • Closes when clicking outside or pressing Escape
  • Checkbox items use role="menuitemcheckbox"
  • Radio items use role="menuitemradio" within role="group"
  • DropdownMenuSearch is a plain text input, and arrow keys still navigate items so keyboard users can search and select without leaving the keyboard

API

DropdownMenu

PropTypeDefaultDescription
openboolean-Controlled open state.
defaultOpenbooleanfalseInitial open state for uncontrolled usage.
onOpenChange(open: boolean) => void-Callback when open state changes.

DropdownMenuTrigger

PropTypeDefaultDescription
asChildbooleanfalseRender as the child element instead of a button.

DropdownMenuContent

Convenience wrapper that renders Portal → Positioner → Popup.

PropTypeDefaultDescription
side"top" | "right" | "bottom" | "left""bottom"Side of the trigger to anchor.
sideOffsetnumber4Offset from the trigger edge in px.
align"start" | "center" | "end""start"Alignment along the side axis.
alignOffsetnumber-Offset from the alignment edge.
collisionPaddingnumber | Padding-Padding from viewport edges for collision detection.
positionerClassNamestring-Additional class for the positioner wrapper.

DropdownMenuSearch

PropTypeDefaultDescription
valuestring-Controlled input value.
onChangehandler-Fired when the user types.
placeholderstring"Search options…"Placeholder text.
autoFocusOnMountbooleantrueFocuses the input as soon as the popup mounts.
prefixReactNode-Slot rendered before the input (e.g. a search icon).
suffixReactNode-Slot rendered after the input (e.g. a clear button).

DropdownMenuItem

PropTypeDefaultDescription
selectedbooleanfalseDraws a checkmark in the right slot. Use for single-select rows.
prefixReactNode-Left-slot content (icon, badge, custom checkbox).
suffixReactNode-Right-slot content. Overrides the selected checkmark.
insetbooleanfalseAdds left padding to align with items that have icons.
actionType"default" | "destructive""default"Visual style. Destructive shows red text.
onSelect(event: MouseEvent) => void-Callback when selected (mapped to onClick).
tooltipReactNode-Shown on hover/focus. DropdownMenuContent wraps items in a shared TooltipProvider, so neighbours show instantly after the first opens.
tooltipSide"top" | "right" | "bottom" | "left""right"Side the tooltip appears on.
disabledbooleanfalseDisables the item.

DropdownMenuCheckboxItem

PropTypeDefaultDescription
checkedboolean-Controlled checked state.
onCheckedChange(checked: boolean) => void-Callback when checked state changes.
closeOnClickbooleantrueSet to false to keep the menu open while toggling multiple options.
showSelectedIndicatorbooleantrueRight-slot confirming checkmark. Set to false for left-checkbox only, the canonical multi-select pattern.

DropdownMenuRadioGroup

PropTypeDefaultDescription
valuestring-Controlled selected value.
onValueChange(value: string) => void-Callback when selection changes.

DropdownMenuRadioItem

PropTypeDefaultDescription
valuestring-Unique value identifying this item.

DropdownMenuSubTrigger

PropTypeDefaultDescription
insetbooleanfalseAdds left padding for alignment.
openOnHoverbooleantrueOpens the submenu when the trigger is hovered or keyboard-highlighted. Focus stays on the parent trigger until the user presses Right Arrow / Enter.
delaynumber100Hover delay in ms before the submenu opens.

DropdownMenuSubContent

PropTypeDefaultDescription
side"top" | "right" | "bottom" | "left""right"Side relative to the sub-trigger.
sideOffsetnumber-2Offset from the sub-trigger edge.
align"start" | "center" | "end""start"Alignment along the side axis.

DropdownMenuLabel

PropTypeDefaultDescription
insetbooleanfalseAdds left padding for alignment.

Other subcomponents

ComponentPurpose
DropdownMenuGroupWraps a related set of items.
DropdownMenuSeparatorFull-width divider between groups of items.
DropdownMenuShortcutRight-aligned keyboard-shortcut hint inside a menu item.

Guidelines

Do

  • Use dropdown menus for contextual actions related to a specific element
  • Group related items with labels and separators
  • Place destructive actions last, separated visually
  • Keep menu items concise by using short, action-oriented labels
  • Use selected on DropdownMenuItem for single-select pickers (filters with one value)
  • Use DropdownMenuCheckboxItem for multi-select pickers (filters with many values)
  • Use DropdownMenuRadioGroup when the values are a fixed enum (sort order, view mode)
  • Add a DropdownMenuSearch when the list could exceed ~10 items
  • Add a left-side icon to every item in a settings or row-actions menu, keeping them in text-tertiary so they don't compete with the label (destructive items skip text-tertiary so the icon takes the destructive color)
  • Style your trigger's open state via the data-popup-open attribute so users can see which trigger owns the visible popover

Don't

  • Don't use dropdown menus for navigation. Use a navigation component instead
  • Don't nest submenus more than one level deep, since it becomes difficult to use
  • Don't place too many items in a single menu without a search input
  • Don't use dropdown menus for form inputs. Use select, combobox, or radio groups instead
  • Don't put both a selected checkmark and a custom suffix on the same DropdownMenuItem, because suffix wins
PreviousDrawer
NextFluid Avatar
Made in NYC© 2026 Rogo Technologies Inc.

Design at Rogo

We’re redesigning an entire industry. Come design it with us.

See open roles

Design at Rogo

  • How we workThe mission of design at Rogo, and the open roles
  • About RogoThe company and the product