Breadcrumb

Free · MIT

Slash, chevron, dot, pill, arrow-step and underline trails with four current-page styles, tones, sizes, a staggered fade or sliding hover underline, and long trails that collapse into a keyboard-friendly menu.

MarkdownOpen in v0

Installation

$ npx shadcn@latest add https://goodui.dev/r/breadcrumb.json

The file lands in components/goodui/breadcrumb.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/breadcrumb works too.

Usage

import Link from "next/link"
import { Home } from "lucide-react"
import { Breadcrumb } from "@/components/goodui/breadcrumb"

export function InvoiceBreadcrumb() {
  return (
    <Breadcrumb
      linkAs={Link}
      maxItems={4}
      items={[
        { label: "Dashboard", href: "/", icon: <Home /> },
        { label: "Clients", href: "/clients" },
        { label: "Maya Chen", href: "/clients/maya-chen" },
        { label: "Invoices", href: "/clients/maya-chen/invoices" },
        { label: "INV-0042" },
      ]}
    />
  )
}

When to use

Use it for
  • Showing where a page sits in a hierarchy and linking back up: Clients / Maya Chen / Invoices / INV-0042.
  • Deep admin pages where the middle of the trail should collapse into a "…" menu on small screens: maxItems.
  • A short, fixed flow shown as connected segments (Cart → Details → Payment): variant="arrow-steps".
Reach for something else
  • A multi-step form where steps have states (done, current, error): use stepper.
  • Top-level navigation between sections of the app: use tabs or bottom-nav.
  • Pages that are not in a hierarchy (a flat list of settings): a page heading is enough.
If you needUse
Default app headervariant="chevron" current="bold" (defaults)
File-path or URL-like trailsvariant="slash"
Very light trails in dense UIsvariant="dot" size="sm"
Chips or filter-like trailsvariant="pill" with current="tone" or "badge"
Short linear flowvariant="arrow-steps"
Long trails on mobilemaxItems={3} (keeps the first item and the last two)
Hover feedback on linksanimation="underline"; animation="fade" staggers the crumbs in on mount

Examples

Arrow steps

<Breadcrumb
  variant="arrow-steps"
  tone="success"
  items={[{ label: "Service", href: "/book" }, { label: "Time", href: "/book/time" }, { label: "Details" }]}
/>

Client-side navigation without hrefs

<Breadcrumb
  variant="pill"
  current="badge"
  items={[
    { label: "Reports", onClick: () => setView("reports") },
    { label: "Revenue", onClick: () => setView("revenue") },
    { label: "October" },
  ]}
/>

API reference

PropTypeDefault
items*
The trail, root first. The last item is the current page.
BreadcrumbItem[]—
variant
Separator and crumb style: slash, chevron (default), dot, pill, arrow-steps or underline.
"slash" | "chevron" | "dot" | "pill" | "arrow-steps" | "underline""chevron"
tone
Color of the current item (tone and badge styles), pill and arrow-step highlights, and focus rings.
"primary" | "neutral" | "success" | "warning" | "danger" | "info""primary"
size"sm" | "md" | "lg""md"
current
Style of the current (last) item.
"bold" | "muted" | "tone" | "badge""bold"
animation
fade staggers the crumbs in on mount; underline grows an underline under hovered links.
"none" | "fade" | "underline""none"
maxItems
Collapse the middle into a "…" menu when there are more items than this. 0 never collapses. Numeric strings work too.
number | string0
itemsBeforeCollapse
Items kept before the "…" when collapsed.
number1
itemsAfterCollapse
Items kept after the "…" when collapsed (including the current page).
number2
separator
Custom separator, replaces the variant's.
React.ReactNode—
linkAs
Link component, e.g. next/link. Receives href, className and children.
React.ElementType—
labels
Text for built-in controls and screen readers; pass translations here.
Partial<BreadcrumbLabels>—

Also accepts every <nav> attribute.

Accessibility

Enter / SpaceFollows a link or opens the "…" menu.
Arrow DownOn the "…" button, opens the menu. Inside the menu, moves to the next hidden page (wraps).
Arrow UpMoves to the previous hidden page in the menu (wraps).
Home / EndMoves to the first or last hidden page in the menu.
EscapeCloses the menu and returns focus to the "…" button.
TabCloses the menu and continues to the next focusable element.
  • A <nav> with aria-label "Breadcrumb" (override with aria-label) around an ordered list.
  • The last item is the current page: it renders as text with aria-current="page", even if it has an href.
  • Separators are aria-hidden list items with role="presentation", so screen readers only read the pages.
  • The "…" button has aria-haspopup="menu", aria-expanded and aria-label "Show 3 more"; the menu is role="menu" named "Hidden pages". Translate both with labels.showMore and labels.hiddenPages.
  • RTL-ready: under dir="rtl" chevrons and arrow-step shapes mirror, the menu opens from the start edge and the fade entrance slides the other way.
  • Opening the menu focuses its first item; clicking outside closes it.

Motion

  • fade: crumbs slide in 6px and fade over 400 ms on mount, 55 ms apart.
  • underline: an underline grows under the hovered link in 280 ms (variant="underline" already shows static underlines and skips it).
  • The "…" menu fades, drops 4px and scales from 0.97 in 160 ms.
  • Reduced motion: no entrance, no underline growth and no transitions; the menu appears instantly.

Notes for AI

Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.

  • Pass the whole trail in items, root first; the last item is always the current page. Do not render separators yourself.
  • Use linkAs={Link} for next/link or react-router; it receives href, onClick, className and children. Items without href but with onClick render as <button>.
  • maxItems collapses only when items.length is above it and above itemsBeforeCollapse + itemsAfterCollapse (defaults 1 and 2).
  • separator replaces the variant's separator for every gap; arrow-steps has no separators.
  • labels={{ showMore: (n) => `${n} weitere anzeigen`, hiddenPages: "Ausgeblendete Seiten" }} translates the menu; the nav name comes from aria-label.

FAQ

How do I use it with Next.js?

Import Link from next/link and pass linkAs={Link}. Every crumb with an href renders through it, including the links in the "…" menu.

Can I show a long trail on mobile?

Set maxItems. The middle collapses into a "…" button that opens a keyboard-friendly menu, and long labels are truncated at 16rem.

Related