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
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
| Prop | Type | Default | Description |
|---|---|---|---|
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. |
search | boolean | { placeholder?; emptyLabel? } | — | Search box that filters rows by label, description and keywords. |
header / footer | ReactNode | — | 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" / 6 | Preferred placement; it flips and shifts to stay on screen. |
closeOnCheck | boolean | false | Whether checkbox and radio rows close the menu. |
width | number | — | Menu width in px. Defaults to the content width, min 216. |
open / defaultOpen / onOpenChange | boolean / boolean / (open: boolean) => void | — | Controlled or uncontrolled open state. |
disabled | boolean | false | Disables the trigger. |
menuClassName | string | — | 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.