Next.js Bootstrap Dropdowns
Next.js Bootstrap dropdown menus that server component pages can render as is: split buttons, alignment, auto close, keyboard support and next/link items.
Dropdowns keep a toolbar or a navbar tidy: a short list of actions or links waits behind one button until the user needs it. AdminKit builds them from three components: Dropdown, DropdownToggle and DropdownMenu. They open Bootstrap's dropdown menus from React state, without Bootstrap's JavaScript. The Floating UI library positions the menu 2px from the toggle and flips it to stay in view. The components are in the free and PRO versions of AdminKit for Next.js.
In the App Router, pages are server components: they render on the server and have no state. A menu needs state to open and close, so the three components are client components: dropdown.tsx starts with "use client". Dropdown keeps the open state itself, so a dropdown with plain links needs no "use client" of its own. It can sit in a server component page, or in a component like ProjectActions below. You only need your own client component for event handlers, such as an onClick on a menu item, because a server component cannot pass functions.
Example
Dropdown adds no class itself, so give it dropdown or btn-group. The toggle needs dropdown-toggle for its caret:
import {
Dropdown,
DropdownMenu,
DropdownToggle,
} from "@/components/bootstrap/dropdown";
export function ProjectActions() {
return (
<Dropdown className="dropdown">
<DropdownToggle as="button" className="btn btn-primary dropdown-toggle">
Actions
</DropdownToggle>
<DropdownMenu as="ul">
<li><a className="dropdown-item" href="#">Edit</a></li>
<li><a className="dropdown-item" href="#">Duplicate</a></li>
<li><hr className="dropdown-divider" /></li>
<li><a className="dropdown-item" href="#">Archive</a></li>
</DropdownMenu>
</Dropdown>
);
}The menu is in the HTML that Next.js builds, hidden until it gets .show. Floating UI runs only in the browser, once the menu opens. So the first render in the browser matches the server's.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | "div" | The wrapper element, such as "li" in a navbar |
display | "dynamic" | "static" | "dynamic" | "dynamic" positions the menu with Floating UI; "static" leaves it where the CSS puts it, as in the navbar |
strategy | "absolute" | "fixed" | "absolute" | "fixed" positions the menu relative to the window, so containers that clip or scroll do not cut it off |
autoClose | boolean | "inside" | "outside" | true | Which clicks close the menu, see Auto close |
drop | "down" | "up" | "start" | "end" | from the class | The side the menu opens on; read from dropup, dropend or dropstart in className |
Dropdown passes its other props, such as className, to the wrapper. The components are in src/components/bootstrap/dropdown.tsx.
DropdownTogglerenders<a href="#" role="button">, or a<button type="button">withas="button". It setsaria-expandedandaria-controls, gets.showwhile open, and still calls youronClickandonKeyDown.DropdownMenuadds.dropdown-menuand takesas("div"by default) andalign("start"or"end";.dropdown-menu-endsets"end").
Split button
A split button keeps the main action one click away and puts the alternatives in the menu. Put a regular button and a toggle with dropdown-toggle-split in a btn-group. The regular button has an onClick, so this one is a client component:
"use client";
import {
Dropdown,
DropdownMenu,
DropdownToggle,
} from "@/components/bootstrap/dropdown";
export function PublishButton({ onPublish }: { onPublish: () => void }) {
return (
<Dropdown className="btn-group">
<button type="button" className="btn btn-success" onClick={onPublish}>
Publish
</button>
<DropdownToggle as="button" className="btn btn-success dropdown-toggle dropdown-toggle-split">
<span className="visually-hidden">More publish options</span>
</DropdownToggle>
<DropdownMenu as="ul">
<li><a className="dropdown-item" href="#">Schedule</a></li>
<li><a className="dropdown-item" href="#">Save as draft</a></li>
</DropdownMenu>
</Dropdown>
);
}onPublish is a function, and a server component cannot pass functions. So the component that renders PublishButton must be a client component too, or PublishButton has to define the handler itself.
Alignment and direction
To move the menu, use Bootstrap's classes, which the components read. dropdown-menu-end on the menu aligns it to the end of the toggle. dropup, dropend or dropstart on the wrapper opens it above, to the right or to the left. The align and drop props do the same without the classes.
<>
<Dropdown className="btn-group me-2">
<DropdownToggle as="button" className="btn btn-secondary dropdown-toggle">End aligned</DropdownToggle>
<DropdownMenu as="ul" className="dropdown-menu-end">
<li><a className="dropdown-item" href="#">Menu item</a></li>
<li><a className="dropdown-item" href="#">Menu item</a></li>
</DropdownMenu>
</Dropdown>
<Dropdown className="btn-group dropup">
<DropdownToggle as="button" className="btn btn-secondary dropdown-toggle">Dropup</DropdownToggle>
<DropdownMenu as="ul">
<li><a className="dropdown-item" href="#">Menu item</a></li>
<li><a className="dropdown-item" href="#">Menu item</a></li>
</DropdownMenu>
</Dropdown>
</>A table or a scrolling card can cut off a menu. Use strategy="fixed" there, as the menus of the tasks board and the AI chat history in AdminKit PRO do.
In the navbar
A navbar menu is an li with display="static", so the navbar's CSS places it. Use Link from next/link for pages of the app, as navbar-user.tsx does:
import Link from "next/link";
import {
Dropdown,
DropdownMenu,
DropdownToggle,
} from "@/components/bootstrap/dropdown";
export function NavbarAccount() {
return (
<Dropdown as="li" className="nav-item dropdown" display="static">
<DropdownToggle className="nav-link dropdown-toggle">Account</DropdownToggle>
<DropdownMenu className="dropdown-menu-end">
<Link className="dropdown-item" href="/pages-profile">Profile</Link>
<Link className="dropdown-item" href="/pages-settings">Settings</Link>
<div className="dropdown-divider" />
<a className="dropdown-item" href="#">Log out</a>
</DropdownMenu>
</Dropdown>
);
}A click on a Link in the menu closes the menu and navigates in the browser, without a full page load.
Auto close
autoClose decides which clicks close an open menu:
autoClose | A click on an item | A click outside |
|---|---|---|
true (default) | Closes the menu | Closes the menu |
"inside" | Closes the menu | Keeps it open |
"outside" | Keeps it open | Closes the menu |
false | Keeps it open | Keeps it open |
With autoClose={false}, only the toggle and Escape close the menu. A link or button inside a <form> in the menu never closes it.
Menu content
Bootstrap's classes work as in HTML: .dropdown-item on a link or button, .dropdown-item-text, .dropdown-header, .dropdown-divider, and .active or .disabled on an item. Bootstrap's dropdown docs list them all.
Accessibility
aria-expandedandaria-controlson the toggle point at the menu, whoseidcomes fromuseId.- Arrow Down or Arrow Up on the toggle opens the menu on its first or last item. In the menu, the arrows move between the enabled items.
- Escape closes the menu and puts the focus back on the toggle.
- Give an icon-only toggle a text for screen readers: a
visually-hiddenspan or anaria-label. - Mark the current page with
.activeandaria-current="page".
Related
- React Bootstrap dropdowns: the same components in AdminKit for React (Vite)
- Bootstrap 5 dropdowns: the dropdowns of the HTML version
- Offcanvas: a side panel for longer menus
- Buttons demo: button dropdowns in every theme color
Last updated on
CollapsePro
A Next.js Bootstrap collapse client component: a panel that opens and closes from React state with Bootstrap's height transition, rendered in the App Router.
List group
Next.js Bootstrap list group markup: static lists in server components, next/link items with the active route from usePathname, badges and cards.