Skip to content
React 19No peer dependencies

Breadcrumb

Shows where a page sits in your app, collapses long trails, works with your router's links and can publish structured data.

Also available for React Native (iOS and Android, beta): Breadcrumb for React Native

Found something wrong, or missing? Open an issue on the bug tracker.

On this page

Installation

npx @grampro/kit add Breadcrumb

The block copies the breadcrumb folder into your project, along with the small shared folder that every component imports. You own the code and can change it freely. The files land in component-lib/, or src/component-lib/ when your project has a src/ folder; pass --dest to put them somewhere else, and later installs follow the first. There are no peer dependencies other than React.

Requirements

  • React 19 and @types/react 19
  • TypeScript target ES2022 or newer, with "jsx": "react-jsx"
  • Browsers from 2024 or newer (the styles use light-dark() and :dir())

Import the stylesheet once, for example in your global CSS:

css
@import "../component-lib/gbs.css";

or in your root layout / entry file:

ts
import "@/component-lib/gbs.css";

The Breadcrumb shows where the current page sits in your app, and lets people go back up the hierarchy in one click. Long trails collapse to their ends behind an ellipsis. Links can be drawn with your router's link component, such as next/link. The trail can also be published as structured data, so search engines can show it in results.

Set the --gbs-* variables on :root to theme every component at once, the grid included. Each component's own variables fall back to them, and then to the built-in palette, so components look identical out of the box.

Default

Live preview

Quick Start

tsx
"use client";
 
import Link from "next/link";
import { Breadcrumb } from "@/component-lib/breadcrumb";
 
export function InvoiceHeader({ invoice }) {
  return (
    <Breadcrumb
      items={[
        { label: "Home", href: "/" },
        { label: "Invoices", href: "/invoices" },
        { label: invoice.number },
      ]}
      renderLink={(props) => <Link {...props} />}
    />
  );
}

Items go from the top level down. The last item is the current page: it isn't a link, and it's marked aria-current="page".

Props Table

Accepts every <nav> attribute, plus:

PropTypeDefaultDescription
itemsBreadcrumbItem[]requiredThe trail, top level first.
separatorReactNodea chevronBetween crumbs, e.g. "/".
maxItemsnumber—Collapse the middle when there are more crumbs than this.
itemsBeforeCollapsenumber1Crumbs kept before the ellipsis.
itemsAfterCollapsenumber1Crumbs kept after the ellipsis, including the current page.
renderLink(props: BreadcrumbLinkProps) => ReactElementan <a>Draw links with your router.
structuredDataboolean | { baseUrl: string }falseAdd schema.org BreadcrumbList JSON-LD. See Structured Data.
size"sm" | "md" | "lg""md"Font size.
aria-labelstringlocaleText.labelName of the navigation landmark.
classNamestring—Class for the <nav>.
classNamesPartial<Record<BreadcrumbSlot, string>>—Slots: root, list, item, link, current, separator, ellipsis.
localeTextPartial<BreadcrumbLocaleText>EnglishSee Locale Text.
FieldTypeDescription
labelReactNodeText shown. Long labels are cut with an ellipsis and the full text in a tooltip.
hrefstringLink target. Omit for the current page or a crumb that isn't a link.
iconReactNodeIcon before the label, e.g. a home icon.
namestringPlain-text name for structured data, when label isn't a string.
onClick() => voidClick handler, with or without href.

Collapsing Long Trails

tsx
<Breadcrumb items={trail} maxItems={4} itemsBeforeCollapse={1} itemsAfterCollapse={2} />

A 7-crumb trail becomes Home › … › Invoices › INV-042. Pressing the ellipsis reveals the full trail and moves keyboard focus to the first crumb that was hidden. Trails with maxItems crumbs or fewer are never collapsed.

A seven-crumb trail at full length and collapsed, and a short one with a custom separator. The last crumb is long enough to be truncated, with the full text in a tooltip.

Live preview

renderLink receives href, className, children and onClick. Spread them onto your link:

tsx
import Link from "next/link";
 
<Breadcrumb items={items} renderLink={(props) => <Link {...props} prefetch={false} />} />;
tsx
// React Router
import { Link } from "react-router";
 
<Breadcrumb items={items} renderLink={({ href, ...props }) => <Link to={href} {...props} />} />;

Structured Data

tsx
<Breadcrumb items={items} structuredData={{ baseUrl: "https://app.example.com" }} />

This adds a <script type="application/ld+json"> with a schema.org BreadcrumbList. Search engines can use it to show the trail under the page title in results. Relative hrefs are resolved against baseUrl, because search engines expect absolute URLs. Render the breadcrumb on the server, as a Next.js page does, so crawlers see the data in the HTML.

Keyboard

KeysAction
TabMove between crumb links and the ellipsis. The current page isn't a stop.
EnterFollow the focused link, or reveal the collapsed crumbs.

Accessibility: the trail is a <nav> landmark named "Breadcrumb", containing an ordered list. Separators are aria-hidden, so screen readers don't read "chevron" between crumbs. The current page has aria-current="page", and the ellipsis says how many crumbs it reveals ("Show 4 more").

Styling and Theming

Rules ship unlayered, so a framework reset cannot strip them and a utility class passed through className still overrides them. Import the generated gbs.css once — see Theming.

tsx
<Breadcrumb classNames={{ current: "text-blue-600", separator: "opacity-40" }} />

CSS variables

Override them on .bc-root, on :root, or through style. Each variable falls back to the shared --gbs-* of the same name, then to the DataGrid's --dg-* when that stylesheet is loaded, and finally to the built-in palette.

VariableUsed for
--bc-font-sizeFont size (set by size).
--bc-fgCurrent page and hovered links.
--bc-mutedLinks and separators.
--bc-hoverEllipsis hover background.
--bc-focusFocus ring.
--bc-max-labelLongest label before it's cut (24ch).

Data attributes

ElementAttributes
Root (nav.bc-root)data-size
Item (li.bc-item)data-index
Current page (.bc-current)aria-current="page"

Locale Text

tsx
<Breadcrumb localeText={{ label: "Brotkrumen", showMore: (count) => `${count} weitere anzeigen` }} />
KeyDefault
label"Breadcrumb"
showMore(count) => "Show {count} more"

Core Exports

ExportDescription
collapseItems(count, maxItems?, before?, after?)The rendered trail: crumb indexes and an ellipsis with the hidden ones.
toBreadcrumbJsonLd(items, baseUrl?)The schema.org BreadcrumbList object.

Next.js

The Breadcrumb is a client component with "use client" at the top of its file, and it renders fully on the server, structured data included.

A Server Component can render it directly with plain items (labels, hrefs and icons). renderLink and onClick are functions, and functions can't be passed from a Server Component to a client component. Put those in a small client wrapper instead, and pass it the items from the page:

tsx
// app/components/AppBreadcrumb.tsx
"use client";
 
import Link from "next/link";
import { Breadcrumb, type BreadcrumbItem } from "@/component-lib/breadcrumb";
 
export function AppBreadcrumb({ items }: { items: BreadcrumbItem[] }) {
  return <Breadcrumb items={items} renderLink={(props) => <Link {...props} />} />;
}

Notes

  • In right-to-left layouts, the chevron separator points the other way automatically.
  • There's no previous breadcrumb component, so there's nothing to migrate.