AdminKitDocs
Navigation

Next.js Bootstrap Breadcrumb

Add a Next.js Bootstrap breadcrumb to App Router pages: static in a server component, or built from usePathname with next/link in a client component.

Pro

This component is only available in AdminKit PRO. Learn more.

On a page a few levels deep, such as one invoice or a project's settings, people want to know where they are and how to get back. A breadcrumb answers both. It lists the path from Home to the current page, and every step above the current page is a link.

AdminKit for Next.js has no breadcrumb component: you write Bootstrap's markup in JSX and use next/link for the links. Breadcrumbs are part of AdminKit PRO. The stylesheet of the free version leaves out Bootstrap's breadcrumb styles.

Plain markup needs no state, so a breadcrumb with fixed steps works in a server component page, without "use client". A breadcrumb that reads the current URL is different: the hook that gives the URL, usePathname(), only works in a client component. Both are below.

Example

When the steps of a page are known, write them in the page itself. Link from next/link works in server components, and the page keeps its metadata export:

src/app/(dashboard)/invoices/2041/page.tsx
import Link from "next/link";
import { pageMetadata } from "@/lib/site";

export const metadata = pageMetadata("/invoices/2041");

export default function InvoicePage() {
  return (
    <>
      <nav aria-label="breadcrumb">
        <ol className="breadcrumb">
          <li className="breadcrumb-item">
            <Link href="/">Home</Link>
          </li>
          <li className="breadcrumb-item">
            <Link href="/invoices">Invoices</Link>
          </li>
          <li className="breadcrumb-item active" aria-current="page">
            Invoice 2041
          </li>
        </ol>
      </nav>
      <h1 className="h3 mb-3">Invoice 2041</h1>
    </>
  );
}

pageMetadata() reads the title and description of a route from src/data/pages.json, so a new route needs an entry there.

Classes

ClassElementEffect
breadcrumb<ol>Lays out the steps in a row, without list bullets
breadcrumb-item<li>One step; adds the divider before every step except the first
activethe last <li>Shows the current page in a muted color; add aria-current="page" too

AdminKit leaves Bootstrap's breadcrumb as it is. For its Sass variables, see Bootstrap's docs.

To give every page a breadcrumb without writing it each time, build the steps from the URL. Each part of the path becomes a step that links to the path up to that part. The labels come from src/data/pages.json, the same list that gives each page its title. A path that is not in the list, such as an invoice number, shows the text from the URL.

usePathname() from next/navigation gives the current path, and the sidebar uses it to highlight the current page. It is a hook, and hooks only run in client components, so this file starts with "use client":

src/components/breadcrumbs.tsx
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import pages from "@/data/pages.json";

export function Breadcrumbs() {
  const pathname = usePathname();
  const segments = pathname.split("/").filter(Boolean);

  const crumbs = [
    { path: "/", label: "Home" },
    ...segments.map((segment, index) => {
      const path = `/${segments.slice(0, index + 1).join("/")}`;
      const page = pages.find((entry) => entry.path === path);
      return { path, label: page?.title ?? decodeURIComponent(segment) };
    }),
  ];

  return (
    <nav aria-label="breadcrumb">
      <ol className="breadcrumb">
        {crumbs.map((crumb, index) =>
          index === crumbs.length - 1 ? (
            <li key={crumb.path} className="breadcrumb-item active" aria-current="page">
              {crumb.label}
            </li>
          ) : (
            <li key={crumb.path} className="breadcrumb-item">
              <Link href={crumb.path}>{crumb.label}</Link>
            </li>
          )
        )}
      </ol>
    </nav>
  );
}

The component looks the titles up in pages.json itself. getPage() from @/lib/site would throw an error for a path that is not in the list.

Render <Breadcrumbs /> from any page, above its title. A server component page can do that: the component takes no props. A client component still renders on the server first, so the static HTML of each page already contains its breadcrumb. usePathname() returns the path without the base path, and next/link adds the base path back, so the links also work when the app runs from a sub-path.

The demo's routes are one level deep, such as /pages-settings. There, the breadcrumb is Home and the page title. It gets longer once your own routes nest, for example /invoices/2041.

Dividers

The slash between the steps is CSS, not markup. To change it on one breadcrumb, set --bs-breadcrumb-divider on the <nav>. Its value is a CSS string, with its own quotes:

<nav
  style={{ "--bs-breadcrumb-divider": "'>'" } as React.CSSProperties}
  aria-label="breadcrumb"
>
  <ol className="breadcrumb">
    <li className="breadcrumb-item">
      <Link href="/">Home</Link>
    </li>
    <li className="breadcrumb-item active" aria-current="page">
      Invoices
    </li>
  </ol>
</nav>

The cast is there because TypeScript does not know custom properties in style. "''" removes the divider. For the whole app, set $breadcrumb-divider in src/scss/1-variables/_app.scss, which loads before Bootstrap.

Accessibility

  • Wrap the list in a <nav> with an aria-label, so screen readers announce it as a navigation region.
  • Give the last step aria-current="page". The active class only changes its color.
  • Use an ordered list (<ol>): the order of the steps carries meaning.
  • The current page is plain text, not a link to itself.

Last updated on

On this page