Skip to content
GetUrUI

Dropdown Menu

New
.md

Menu button with button, icon, avatar or account-card triggers, an account header or filtering search, sections, checkbox and radio rows and submenus; it springs open from the trigger and a highlight glides between rows.

dropdownmenumenu buttonaccount menuactionssearchcheckboxradiosubmenukeyboard

Installation

$npx shadcn@latest add @geturui/dropdown
Download source (.zip)

Also installs: Button

Usage

usage.tsx
1import { Copy, Pencil, Trash2 } from "lucide-react";
2import { Dropdown } from "@/components/ui/dropdown";
3
4export function RowActions() {
5  return (
6    <Dropdown
7      trigger={{ kind: "icon", label: "Row actions" }}
8      align="end"
9      items={[
10        { type: "label", label: "Component" },
11        { label: "Rename", icon: <Pencil />, shortcut: "F2", onSelect: () => {} },
12        { label: "Duplicate", icon: <Copy />, shortcut: "⌘D", onSelect: () => {} },
13        { type: "separator" },
14        { label: "Delete", icon: <Trash2 />, danger: true, onSelect: () => {} },
15      ]}
16    />
17  );
18}

Props

PropTypeDefaultDescription
trigger*{ kind: "button" | "icon"; label; icon?; variant?; size? } | { kind: "avatar"; name; initials? } | { kind: "account"; name; email? } | (props) => ReactNode—Preset trigger, or a render function that spreads the given id, aria and handler props on your own element.
items*DropdownEntry[]—Rows { label, icon?, description?, shortcut?, badge?, onSelect?, disabled?, danger?, type?: "checkbox" | "radio", checked?, items?, keywords? }, { type: "separator" } or { type: "label", label } to start a section.
account{ name; email; initials?; badge? }—Account card with initials avatar at the top of the menu.
searchboolean | { placeholder?; emptyLabel? }—Search box that filters rows by label, description and keywords.
header / footerReactNode—Extra content above the rows, and a non-interactive line under them (version, plan).
side / align / sideOffset"top" | "bottom" / "start" | "center" | "end" / number"bottom" / "start" / 6Preferred placement; it flips and shifts to stay on screen.
closeOnCheckbooleanfalseWhether checkbox and radio rows close the menu.
widthnumber—Menu width in px. Defaults to the content width, min 216.
open / defaultOpen / onOpenChangeboolean / boolean / (open: boolean) => void—Controlled or uncontrolled open state.
disabledbooleanfalseDisables the trigger.
menuClassNamestring—Classes for every menu surface, including submenus.

Accessibility

  • APG menu button: the trigger has aria-haspopup, aria-expanded and aria-controls; the menu is labelled by it. Enter, Space and Arrow Down open on the first row, Arrow Up on the last.
  • Rows are menuitem, menuitemcheckbox or menuitemradio with aria-checked; sections are role="group" labelled by their heading. Up / Down wrap, Home / End jump, typing jumps to a matching row, Right opens and Left closes a submenu.
  • Escape closes and returns focus to the trigger; Tab and outside clicks close without stealing focus. With search, focus starts in the box, arrows move into the list and the result count is announced.
  • The menu is portalled, flips and shifts to stay in the viewport, and falls back to a plain fade with reduced motion.

Related