ComponentsOverlays

Dropdown Menu

A list of actions behind a button. Use it for row and page actions, bulk actions on a narrow toolbar, filters, and account menus. Arrow keys move through items, and focus returns to the trigger when it closes.

Usage

Dropdown menu usageTSX
import { Button } from "@tulipfarm/sprout/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@tulipfarm/sprout/dropdown-menu";

<DropdownMenu>
  <DropdownMenuTrigger
    render={<Button variant="ghost" size="icon-sm" aria-label="Actions for row 1042" />}
  >
    <MoreH size={16} aria-hidden="true" />
  </DropdownMenuTrigger>
  <DropdownMenuContent align="end">
    <DropdownMenuGroup>
      <DropdownMenuItem onClick={edit}>Edit row</DropdownMenuItem>
      <DropdownMenuItem onClick={history}>History</DropdownMenuItem>
    </DropdownMenuGroup>
    <DropdownMenuSeparator />
    <DropdownMenuItem variant="destructive" onClick={remove}>
      Delete row
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

Pass a Button to DropdownMenuTrigger through render. Icon-only triggers need an aria-label. Disable the trigger with disabled on either part, and set aria-busy while work runs.

Sizes

DropdownMenuContent size is "anchor" (the default: as wide as the trigger, at least 8rem) or "menu" (a fixed 16rem). Use anchor for text triggers like “Selected actions” and short row menus. Use menu for icon triggers with icon items, longer labels, and two-line choices. Both stay inside the viewport and scroll when tall.

Row and page actions

A More button opens actions for one row or page. Icon triggers align the menu to their end.

Table rowghost icon-sm · anchor · group, separator, destructive
Table optionsoutline icon-sm · anchor
File pageoutline icon · menu · icon items
Files rowghost icon-sm · menu · two separators · busy row disables the trigger
Project filesghost sm Add · ghost icon-sm row · disabled item
Chat list rowghost icon-sm · menu · label changes with state
Calendarghost calendar-icon · menu
Google DriveTrigger ref returns focus after the dialog · link item

Placement

Set side, align, sideOffset, and alignOffset. Menus open below and start-aligned by default. Composers and footers use side="top". The menu flips when there is no room.

Chat

The composer opens upward with two-line choices. The header menu disables actions while a reply is sending.

Composer filesghost sm · menu · side top · label
Chat headerghost icon-sm · menu · disabled while sending

States

A busy trigger changes its label and sets aria-busy. Items take disabled; they stay readable at half opacity and can’t be chosen. When work finishes, show the result next to the trigger and offer only the actions that still apply.

Selection actions

Narrow toolbars fold bulk actions into one menu. The trigger shows progress and turns busy while work runs.

3 rows selectedoutline sm · anchor · loading, then an outcome menu
2 files selectedoutline sm · anchor · items disabled while moving

Checkboxes and radios

DropdownMenuCheckboxItem and DropdownMenuRadioItem show a check on the right. They keep the menu open so people can change several options; filters also pass closeOnClick={false} to say so outright. Pass closeOnClick to close after one choice.

Checkbox menus

Filters and column pickers keep the menu open with closeOnClick={false}. The last visible column can’t be hidden.

Status filteroutline sm · anchor · invalid when empty
Columnsoutline sm with icon and count badge · label

Links and submenus

Render an item as a link with render={<a href="…" />} or your router’s link. DropdownMenuSub nests a menu: the DropdownMenuSubTrigger adds a chevron and opens DropdownMenuSubContent to the side. Long names truncate inside the 16rem menu.

Sidebar menus

The workspace menu opens below with a switcher submenu. The account menu opens upward with a theme submenu. Items are links.

Controlled

Control the menu with open and onOpenChange to open it from a shortcut. finalFocus on the content decides where focus lands when it closes.

Controlled

Focus the card and press Shift + F10 or the Menu key. The menu opens through open and onOpenChange, then finalFocus returns to the card.

Menubar

Inside Base UI MenubarTSX
import { Menubar } from "@base-ui/react/menubar";

<Menubar>
  <DropdownMenu>
    <DropdownMenuTrigger className="menubar-trigger">File</DropdownMenuTrigger>
    <DropdownMenuContent alignOffset={-4} sideOffset={8} className="min-w-48">
      <DropdownMenuItem>New task<DropdownMenuShortcut>⌘ N</DropdownMenuShortcut></DropdownMenuItem>
      <DropdownMenuItem inset>Duplicate</DropdownMenuItem>
    </DropdownMenuContent>
  </DropdownMenu>
</Menubar>

Menubar

Each menu is a DropdownMenu inside Base UI’s Menubar. Arrow keys move between menus; inset aligns text with icon rows.

Accessibility

The trigger has aria-haspopup="menu" and aria-expanded. Enter, Space, and Arrow Down open the menu. Arrow keys move, typing jumps to a matching item, Arrow Right opens a submenu and Arrow Left closes it, and Escape closes the menu and returns focus. Disabled items can still be reached with arrow keys so people know they exist, but they can’t be chosen.

Menus fade and scale in from the trigger over 120ms. Motion turns off when reduced motion is requested. In forced-colors mode the highlighted item gets an outline.

Theme tokens

Menus use --sprout-surface, --sprout-line, --sprout-hover, --sprout-text, --sprout-muted, --sprout-danger, --sprout-danger-subtle, --sprout-shadow-overlay, and the panel and control radii. They need no brand color, so they look the same with the neutral and TulipFarm themes in light and dark mode.