How to Add a Dropdown to a React App
Step-by-step guide to adding a copy-and-own dropdown menu to any React project with the Drivn CLI — dot-notation API, outside-click dismiss, no portal.
A dropdown menu is a small panel that opens under a trigger button — an account menu in a header, a row of actions on a table, a kebab on a card. Building one well means tracking open state, positioning the panel under the button, closing it when you click anywhere else, and animating the open and close. Most teams reach for a floating-position library and a portal on top of a menu primitive to get all of that, and every dependency adds runtime JavaScript.
Drivn writes the component into your repository instead. The CLI copies a dropdown.tsx file built on plain React — useState tracks open and closed, a single mousedown listener on the document closes the menu on outside-click, and a CSS transition on opacity and scale handles the animation. There is no floating-ui, no portal, and no menu library in your package.json. The API is dot notation: Dropdown.Trigger opens, Dropdown.Content holds the panel, and Dropdown.Item is a single action with an icon prop and a destructive flag. After install the file is yours to read and restyle. Because it holds state with hooks and listens for outside clicks in an effect, it is a client component marked 'use client', while the page around it can still render on the server.
This guide adds the Dropdown to an existing React app in about ten minutes — install the CLI, render a basic actions menu, group items with labels and separators, and align the panel to either edge. It works the same in Vite + React or a Next.js App Router project. For the Next.js-specific walkthrough see the Next.js Dropdown guide; for live patterns see the Dropdown examples.
Prerequisites
Before installing the Dropdown, confirm your React project has the three things Drivn assumes: Tailwind CSS v4 installed and processing your CSS, TypeScript configured (the component ships as a .tsx file), and a @/ path alias pointing at your source directory. If you scaffolded with create-next-app, npm create vite, or npx drivn@latest create, all three are already wired. For a custom setup, check the compilerOptions.paths entry in tsconfig.json; the installation page lists the minimal config. The Dropdown composes Drivn's own Button for its trigger, so the CLI writes that file too, and it adds lucide-react to your package.json if it is missing — you use the icons on menu items via the icon prop.
Step 1 — Install Drivn via the CLI
Run the CLI from your project root to add the Dropdown source file. The command prompts once for your install directory (defaulting to src/components/ui/), writes dropdown.tsx, and resolves its component dependency by also writing button.tsx, since the Dropdown imports Button directly for its trigger. It adds lucide-react to your package.json if it is not already present. No global config file is created — both files are TypeScript you edit like any other component. Confirm they landed in your editor, then commit them. If your project uses a monorepo layout or a non-standard path, the CLI docs cover the flags for targeting a custom location during install.
1 # add the Dropdown (the CLI also writes its Button dependency) 2 npx drivn add dropdown 3 4 # verify both files were written 5 ls src/components/ui/dropdown.tsx src/components/ui/button.tsx
Step 3 — Group items with labels and separators
A real menu is more than a flat list. Dropdown.Group wraps a related set with a py-1 rhythm, Dropdown.Label renders a small muted heading above a group at px-3 py-1.5 text-xs font-medium text-muted-foreground, and Dropdown.Separator draws a one-pixel divider (my-1 h-px bg-border) between sections. The pattern matches how Linear and Notion structure their context menus — a label sets context, the items act on it, a separator breaks to the next group. Keep each group to three to five items; if a group grows past five the menu is doing too much and the actions belong in Tabs or a Sidebar instead. The structure below composes an account menu with a labeled group, a separator, and a destructive sign-out. For live variations see the Dropdown examples.
1 <Dropdown.Content> 2 <Dropdown.Group> 3 <Dropdown.Label>Account</Dropdown.Label> 4 <Dropdown.Item icon={User}>Profile</Dropdown.Item> 5 <Dropdown.Item icon={Settings}>Settings</Dropdown.Item> 6 </Dropdown.Group> 7 <Dropdown.Separator /> 8 <Dropdown.Item icon={LogOut} destructive> 9 Sign out 10 </Dropdown.Item> 11 </Dropdown.Content>
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
Frequently asked questions
Yes. Set the project up with TypeScript and Tailwind v4 first, then run npx drivn add dropdown. The Dropdown has no dependency on Next.js or any router — it renders anywhere React and Tailwind reach the DOM. Vite + React is the most common non-Next setup and works without extra configuration; the CLI also writes the Button dependency the Dropdown uses for its trigger and adds lucide-react for the menu-item icons.
The Dropdown is a client component — it is marked 'use client' because it tracks open state with useState and listens for outside clicks in an effect. Your surrounding page and layout still render on the server. The usual pattern is a small client component holding the Dropdown and its trigger, rendered as an island inside an otherwise server-rendered route. Next.js inserts the client boundary at the import automatically, with no dynamic() import required.
No. The panel is a plain absolute top-full mt-1 element positioned directly under the trigger inside the same wrapper <div>, so there is no portal and no floating-ui or popper dependency. Dismissal is a single mousedown listener on the document that closes the menu when the click lands outside the wrapper ref. The whole behavior lives in the one file the CLI writes, plus Drivn's own Button for the trigger.
You do not have to wire anything — the Item source calls setOpen(false) immediately after running your onClick handler, so selecting any item closes the menu automatically. Clicking the trigger again or clicking anywhere outside the wrapper also closes it. If you need an item that keeps the menu open, that is a small edit to the Item function in the source file you own after install.
No — the Drivn Dropdown is a click-driven menu. It opens on trigger click, closes on outside-click or item-click, and does not handle arrow keys, typeahead, Escape, submenus, or checkbox and radio items. If your menu needs the full ARIA keyboard pattern, pick shadcn/ui's Radix-backed DropdownMenu for that surface — see Drivn vs shadcn Dropdown. The two libraries can coexist in the same project.

