AdminKitDocs
Components

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:

src/components/project-actions.tsx
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

PropTypeDefaultDescription
asElementType"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
autoCloseboolean | "inside" | "outside"trueWhich clicks close the menu, see Auto close
drop"down" | "up" | "start" | "end"from the classThe 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.

  • DropdownToggle renders <a href="#" role="button">, or a <button type="button"> with as="button". It sets aria-expanded and aria-controls, gets .show while open, and still calls your onClick and onKeyDown.
  • DropdownMenu adds .dropdown-menu and takes as ("div" by default) and align ("start" or "end"; .dropdown-menu-end sets "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:

src/components/publish-button.tsx
"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:

src/components/navbar/navbar-account.tsx
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:

autoCloseA click on an itemA click outside
true (default)Closes the menuCloses the menu
"inside"Closes the menuKeeps it open
"outside"Keeps it openCloses the menu
falseKeeps it openKeeps 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.

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-expanded and aria-controls on the toggle point at the menu, whose id comes from useId.
  • 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-hidden span or an aria-label.
  • Mark the current page with .active and aria-current="page".

Last updated on

On this page