React Dropdown Component Examples
Drop-in React Dropdown examples: actions menu, right-aligned, grouped items, user avatar menu, and table-row kebab. Zero runtime deps, dot notation API.
Menus that fire actions — Edit, Duplicate, Delete on a Table row, the avatar menu in a header, a kebab tucked into a Card corner — want the smallest surface that does the job. Drivn's Dropdown is one file of just under two hundred lines with zero runtime UI dependencies. useState on the root holds open state, a mousedown listener on document closes the panel when the click lands outside the wrapper ref, and transition-[opacity,scale] duration-150 ease-out carries the entrance. The content never unmounts — it swaps between opacity-100 scale-100 and opacity-0 scale-95 pointer-events-none.
The API is dot notation. Dropdown.Trigger toggles, Dropdown.Content renders the panel, Dropdown.Item is one action with an icon prop and a destructive flag, and Dropdown.Group, Dropdown.Label, and Dropdown.Separator add structure. The trigger renders Drivn's Button with variant="outline" and rounded="md" already applied, so children become the label and nothing needs composing. align on the root picks left-0 or right-0 for the content.
Every snippet below imports from @/components/ui/dropdown, the path the CLI installs to, so each one compiles the moment npx drivn add dropdown finishes. Keyboard navigation, submenus, and checkbox items are out of scope here — that comparison covers when Radix is the better fit. What follows is the click-driven shape: an actions menu with icons, a right-aligned menu, grouped items, an avatar menu, and a table-row kebab.
Grouped items with labels and separators
For menus with more than four or five actions, group related items together. Dropdown.Group wraps a section with a py-1 rhythm, Dropdown.Label renders a muted heading at px-3 py-1.5 text-xs font-medium text-muted-foreground, and Dropdown.Separator draws a 1px horizontal line via my-1 h-px bg-border. The pattern matches how Gmail, Linear, and Notion structure their context menus — a label sets context, the items underneath act on that context, a separator breaks to the next group.
Keep groups short — three to five items max — and label each one with one or two words. If a group grows beyond five items, the menu is doing too much; split the actions into a Tabs or Sidebar instead. The Dropdown.Group element exists for visual rhythm only; it has no semantic role attached.
1 import { Dropdown } from "@/components/ui/dropdown" 2 import { Edit, Copy, Share2, Archive, Trash2 } from "lucide-react" 3 4 export default function Page() { 5 return ( 6 <Dropdown> 7 <Dropdown.Trigger>Manage</Dropdown.Trigger> 8 <Dropdown.Content> 9 <Dropdown.Group> 10 <Dropdown.Label>Edit</Dropdown.Label> 11 <Dropdown.Item icon={Edit}>Rename</Dropdown.Item> 12 <Dropdown.Item icon={Copy}>Duplicate</Dropdown.Item> 13 </Dropdown.Group> 14 <Dropdown.Separator /> 15 <Dropdown.Group> 16 <Dropdown.Label>Share</Dropdown.Label> 17 <Dropdown.Item icon={Share2}>Share link</Dropdown.Item> 18 </Dropdown.Group> 19 <Dropdown.Separator /> 20 <Dropdown.Group> 21 <Dropdown.Label>Danger zone</Dropdown.Label> 22 <Dropdown.Item icon={Archive}>Archive</Dropdown.Item> 23 <Dropdown.Item icon={Trash2} destructive> 24 Delete 25 </Dropdown.Item> 26 </Dropdown.Group> 27 </Dropdown.Content> 28 </Dropdown> 29 ) 30 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
You do not need to — Dropdown.Item wraps your onClick and calls setOpen(false) automatically after your handler runs. The wrapper lives inside the registry source: each Item button invokes the consumer's onClick, then closes the menu in the same click handler. If you need the menu to stay open after an item-click (rare, but useful for confirm-then-act flows), edit the local copy at @/components/ui/dropdown and remove the setOpen(false) line from the Item handler, or skip Dropdown.Item and render a plain <button> inside Dropdown.Content.
The trigger renders Drivn's Button component under the hood with variant="outline" and rounded="md" wired in. Whatever children you pass become the button label, so an icon, an avatar, or a mixed icon-plus-text label all work — you just give up the ability to render a non-button element at the trigger position. If you need the trigger to be a link, an avatar without the outline ring, or a custom element, edit the Trigger function inside @/components/ui/dropdown after install. The file lives in your repo, and the change is local to your project.
No — Dropdown.Item is a single flat action, and there is no Dropdown.Sub or Dropdown.SubContent in the registry source. The component is designed for short, click-driven menus where every action lives at the top level. If your menu needs hierarchical actions or a Files > Recent > Yesterday-style cascade, reach for shadcn/ui's Radix-backed DropdownMenu for that specific surface — see Drivn vs shadcn Dropdown for the comparison. The two libraries can coexist in the same project.
The dropdown content is positioned with absolute top-full mt-1 on styles.content and does not portal out of the DOM. That means an overflow: hidden ancestor can clip the menu when it opens. The fix is to give the ancestor overflow: visible for that one container, or move the dropdown outside the clipped subtree. If portaling is a hard requirement — long lists of items inside a virtualised table is the classic case — pick the Radix-backed DropdownMenu through shadcn/ui, which renders its content through a portal by default.
The Dropdown file starts with "use client" because it manages state with useState and attaches a mousedown listener through useEffect. You can import it from a Server Component, but it renders on the client. Next.js draws the client boundary correctly based on the directive at the top of the file — no manual configuration needed in your layout or page. Render the trigger and content inside any server-rendered page and the dropdown hydrates on the client automatically.