Breadcrumb
Free · MITSlash, 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.
Installation
$ npx shadcn@latest add https://goodui.dev/r/breadcrumb.jsonThe 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
- 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".
- 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 need | Use |
|---|---|
| Default app header | variant="chevron" current="bold" (defaults) |
| File-path or URL-like trails | variant="slash" |
| Very light trails in dense UIs | variant="dot" size="sm" |
| Chips or filter-like trails | variant="pill" with current="tone" or "badge" |
| Short linear flow | variant="arrow-steps" |
| Long trails on mobile | maxItems={3} (keeps the first item and the last two) |
| Hover feedback on links | animation="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
| Prop | Type | Default |
|---|---|---|
| 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 | string | 0 |
| itemsBeforeCollapse Items kept before the "…" when collapsed. | number | 1 |
| itemsAfterCollapse Items kept after the "…" when collapsed (including the current page). | number | 2 |
| 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 / Space | Follows a link or opens the "…" menu. |
| Arrow Down | On the "…" button, opens the menu. Inside the menu, moves to the next hidden page (wraps). |
| Arrow Up | Moves to the previous hidden page in the menu (wraps). |
| Home / End | Moves to the first or last hidden page in the menu. |
| Escape | Closes the menu and returns focus to the "…" button. |
| Tab | Closes 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
Horizontal or vertical stepper where the connecting line fills between steps, numbers morph into drawn checkmarks, a current-step ring glides to the active step, errors shake, and completed steps are clickable.
Six variants (underline, pill, segmented, enclosed folder tabs, soft, vertical line), a sliding, fading or static indicator, fade or directional slide panels, tones, sizes, shapes, shadows and orientation, with icons, count badges, arrow keys and a one-line items API.
Inline link with seven underline styles (static, slide-in, grow from center, marker sweep, nudging arrow), line decorations, tones, leading icons and automatic external-link handling. Works with next/link via as.
Line, dashed, dotted, gradient, double, glow and ornament separators with diamond, dot or icon marks, labels at the center, start or end, tones, thickness, vertical orientation, a draw-in-on-view and a shimmer animation.