Summit
ComponentsOverlays

Dropdown Menu

A Dropdown Menu allows users to choose an action from a list that opens from a button.

When to Use

  • for the actions on one record, such as “Open”, “Send reminder”, and “Void” on a row of invoices
  • for the account actions at the foot of a Sidebar
  • to change settings that apply immediately, with checkbox items, like the columns that a table shows
  • to choose one option among a few, with radio items, like the sort order of a table

When Not to Use

  • to choose a value for a form, as a menu runs actions and has no value (use a Select)
  • for commands numerous enough to need a search (use Command)
  • to repeat the actions of a record on a right click (add a Context Menu)
  • for the menus of an editor, side by side (use a Menubar)
  • for links to the sections of a site (use a Navigation Menu)
Dropdown MenuContext MenuMenubarNavigation Menu
PurposeActions on one record or controlThe same actions, at the pointerAn editor’s commands, in menusLinks to the sections of a site
OpeningA click on a buttonA right click or a long pressA click on a name in the barThe pointer over a trigger, or a click
PositionBelow the buttonAt the pointerBelow the nameBelow the trigger
ContentMenu itemsMenu itemsMenu itemsLinks, in any layout
Page BehindLockedLockedLockedInteractive
Rolemenumenumenubar, with a menu per nameNone. The links are in a nav element.

Anatomy

ElementUsage
TriggerRequired
Group labelOptional
ItemRequired
ShortcutOptional
PopupRequired
Submenu triggerOptional
SeparatorOptional
Checkbox itemOptional
CheckOn a checked item
Radio itemOptional

DropdownMenuContent renders the portal, the positioner, and the popup, so you don’t need to add them. DropdownMenuSubTrigger adds the caret, and a checkbox or a radio item adds the check.

Item Types

A Context Menu and a Menubar have the same parts under their own prefixes, such as ContextMenuItem.

PartPurposeClick
DropdownMenuItemAn actionRuns it and closes the menu
DropdownMenuCheckboxItemA setting that is on or offChanges it. The menu stays open.
DropdownMenuRadioItemOne option of a DropdownMenuRadioGroupSelects it. The menu stays open.
DropdownMenuSubTriggerThe name of a submenuOpens the submenu
DropdownMenuLabelThe name of a groupNone. It can’t receive focus.
DropdownMenuSeparatorA line between groupsNone
DropdownMenuShortcutThe keys that run an item, as text at its endNone. It doesn’t bind a key.

DropdownMenuShortcut only shows the keys, so listen for them in your application. To show a key outside a menu, use a Kbd.

If you want to change whether a click on an item closes the menu, set closeOnClick on that item.

To align an item, a label, or a submenu trigger with the text of an item that has an icon, set inset.

Variants

Set variant on DropdownMenuItem.

VariantPurpose
defaultAny action
destructiveAn action that deletes or cancels a record, such as “Void” on an invoice

Place a destructive item last and below a separator so that it is set apart from the other actions.

Behavior

By default, a Dropdown Menu is modal: while it is open, Base UI locks the page’s scroll and prevents clicks from reaching the page. If the menu is opened by touch, the page can still scroll. If you want the page to stay interactive, set modal={false} on DropdownMenu.

Position

By default, the menu opens below the trigger and is aligned with its start edge. If the menu doesn’t fit there, Base UI moves it to another side. To change its position, use side, sideOffset, align, and alignOffset on DropdownMenuContent. For example, set align="end" for the menu at the end of a table row. A submenu opens at the end edge of its trigger.

Size and Overflow

By default, the menu is as wide as its trigger, with a minimum width of 128px, and long items will wrap. We recommend setting a width on DropdownMenuContent, for example w-48. If the menu is taller than the space available for it, it scrolls inside the popup.

Content

StringRuleExampleCounterexample
ItemSentence case“Send reminder”“Send Reminder”
ItemStart an item that runs an action with a verb“Send reminder”“Reminder”
LabelSay what the group’s items share“Sort by”“Options”

Accessibility

Base UI’s Menu sets the roles, moves focus into the menu, and returns it to the trigger. You provide the name of the trigger.

Keyboard

KeyResult
Enter, Space, or ArrowDown on the triggerOpens the menu. Focus moves to the first item.
ArrowUp on the triggerOpens the menu. Focus moves to the last item.
ArrowDown, ArrowUpMoves focus to the next or the previous item, including disabled items. From the last item, focus moves to the first.
Home, EndMoves focus to the first or the last item
A letterMoves focus to the next item that starts with it
Enter or Space on an itemRuns it and closes the menu. Focus returns to the trigger. A disabled item does nothing.
Enter or Space on a checkbox or a radio itemChanges it. The menu stays open.
ArrowRight, Enter, or Space on a submenu triggerOpens the submenu. Focus moves to its first item.
ArrowLeft in a submenuCloses the submenu. Focus returns to its trigger.
EscapeCloses the menu and returns focus to the trigger. In a submenu, closes only the submenu.
TabCloses the menu. Focus moves to the control after the trigger.
Shift+TabCloses the menu. Focus returns to the trigger.

Requirements

An icon-only trigger has no text, so it needs an aria-label for screen readers to announce it, as WCAG 2.2 SC 4.1.2 Name, Role, Value requires. The menu is named by its trigger, so one label names both. If the trigger repeats in a list, name the record, for example “Actions for INV-2026-014”.

Place a DropdownMenuLabel inside a DropdownMenuGroup or a DropdownMenuRadioGroup. If a label is outside a group, Base UI throws an error.

Set aria-hidden on an icon in an item. See Iconography for the rule.

Installation

npx shadcn@latest add @summit/dropdown-menu

Usage

To run an action from an item, pass it to onClick. DropdownMenuTrigger has no style of its own, so pass it a Button through render.

import { Button } from '@/components/ui/button';
import {
    DropdownMenu,
    DropdownMenuContent,
    DropdownMenuItem,
    DropdownMenuSeparator,
    DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';

<DropdownMenu>
    <DropdownMenuTrigger render={<Button variant="outline" />}>Account</DropdownMenuTrigger>
    <DropdownMenuContent className="w-48">
        <DropdownMenuItem onClick={openProfile}>Profile</DropdownMenuItem>
        <DropdownMenuItem onClick={openBilling}>Billing</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="destructive" onClick={signOut}>
            Sign out
        </DropdownMenuItem>
    </DropdownMenuContent>
</DropdownMenu>;

On a Table Row

For the menu of a table row, pass the trigger an icon-only Button that names the record, and set align="end" so that the end edge of the menu is aligned with the end edge of the Button.

On a Table Row
import { DotsThreeIcon } from '@phosphor-icons/react';

<DropdownMenu>
    <DropdownMenuTrigger render={<Button variant="ghost" size="icon-sm" aria-label="Actions for INV-2026-014" />}>
        <DotsThreeIcon weight="bold" />
    </DropdownMenuTrigger>
    <DropdownMenuContent align="end" className="w-40">
        <DropdownMenuItem>Open</DropdownMenuItem>
        <DropdownMenuItem>Send reminder</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="destructive">Void</DropdownMenuItem>
    </DropdownMenuContent>
</DropdownMenu>;

With Checkbox Items

To keep the settings in the parent, pass checked and onCheckedChange to the checkbox items.

With Checkbox Items
import { useState } from 'react';

const [columns, setColumns] = useState({ client: true, project: false });

<DropdownMenuContent className="w-48">
    <DropdownMenuGroup>
        <DropdownMenuLabel>Show columns</DropdownMenuLabel>
        <DropdownMenuCheckboxItem
            checked={columns.client}
            onCheckedChange={(client) => setColumns({ ...columns, client })}
        >
            Client
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem
            checked={columns.project}
            onCheckedChange={(project) => setColumns({ ...columns, project })}
        >
            Project
        </DropdownMenuCheckboxItem>
    </DropdownMenuGroup>
</DropdownMenuContent>;

With Radio Items

To keep the choice in the parent, pass value and onValueChange to DropdownMenuRadioGroup.

With Radio Items
import { useState } from 'react';

const [sort, setSort] = useState('due');

<DropdownMenuContent className="w-48">
    <DropdownMenuGroup>
        <DropdownMenuLabel>Sort by</DropdownMenuLabel>
        <DropdownMenuRadioGroup value={sort} onValueChange={setSort}>
            <DropdownMenuRadioItem value="due">Due date</DropdownMenuRadioItem>
            <DropdownMenuRadioItem value="amount">Amount</DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
    </DropdownMenuGroup>
</DropdownMenuContent>;

With an Icon and a Shortcut

Place the icon first and the DropdownMenuShortcut last.

With an Icon and a Shortcut
import { CopyIcon } from '@phosphor-icons/react';

<DropdownMenuItem onClick={duplicate}>
    <CopyIcon aria-hidden />
    Duplicate
    <DropdownMenuShortcut>⌘D</DropdownMenuShortcut>
</DropdownMenuItem>;

If an item navigates to another page, pass an a to render on DropdownMenuItem. The item keeps role="menuitem", and a click or Enter follows the link.

Links as Items
<DropdownMenuItem render={<a href="/settings/billing" />}>Billing</DropdownMenuItem>

Open a Dialog From an Item

To open a Dialog from an item, keep the Dialog’s open state in the parent and set it in the item’s onClick. Place the Dialog next to the menu, not inside it. See the Base UI Menu documentation for this pattern.

Open a Dialog From an Item
import { useState } from 'react';

import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';

const [renaming, setRenaming] = useState(false);

<>
    <DropdownMenu>
        <DropdownMenuTrigger render={<Button variant="outline" />}>Project</DropdownMenuTrigger>
        <DropdownMenuContent className="w-48">
            <DropdownMenuItem onClick={() => setRenaming(true)}>Rename</DropdownMenuItem>
        </DropdownMenuContent>
    </DropdownMenu>
    <Dialog open={renaming} onOpenChange={setRenaming}>
        <DialogContent>
            <DialogTitle>Rename project</DialogTitle>
        </DialogContent>
    </Dialog>
</>;

API Reference

DropdownMenuShortcut accepts the props of a span. The other parts accept the props of their matching parts in Base UI’s Menu, where DropdownMenuContent is Menu.Popup, DropdownMenuLabel is Menu.GroupLabel, and DropdownMenuSub is Menu.SubmenuRoot. See the Base UI Menu documentation for open, onOpenChange, modal, closeOnClick, checked, onCheckedChange, value, onValueChange, and OffsetFunction.

PropTypeDefaultDescription
side'top' | 'bottom' | 'left' | 'right' | 'inline-start' | 'inline-end''bottom'The side of the trigger the menu opens on
sideOffsetnumber | OffsetFunction4The gap between the trigger and the menu, in pixels
align'start' | 'center' | 'end''start'The edge of the trigger the menu is aligned with
alignOffsetnumber | OffsetFunction0A shift along that edge, in pixels

DropdownMenuSubContent accepts the same props. Its defaults are 'inline-end', 0, 'start', and -3.

PropTypeDefaultDescription
variant'default' | 'destructive''default'The color of the text and of the highlight
insetbooleanfalseAligns the item with the text of an item that has an icon

DropdownMenuLabel, DropdownMenuSubTrigger, DropdownMenuCheckboxItem, and DropdownMenuRadioItem also accept inset.

On this page