Navigation

Breadcrumbs

An accessible hierarchical breadcrumb trail with collapsible overflow — keeps deep paths short until the consumer asks to see the middle.

Preview in your theme

Loading preview…

"use client"

import * as React from "react"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"

export interface BreadcrumbItem {
  label: string
  /** Omit on the last (current) item — it renders as text with aria-current="page". */
  href?: string
}

export interface BreadcrumbsProps extends React.HTMLAttributes<HTMLElement> {
  items: BreadcrumbItem[]

Installation

npx shadcn@latest add https://ui.zyeon.ai/r/breadcrumbs.json

Prompt

The prompt behind this component — paste it into your AI assistant to recreate or adapt it.

Build a React + TypeScript + Tailwind "Breadcrumbs" component using
lucide-react for icons.

Contract
- export const Breadcrumbs = React.forwardRef<HTMLElement, BreadcrumbsProps>
  rendering a <nav>; spread remaining native nav props on the root, merge
  className via cn().
- items: { label: string; href?: string }[] — the last entry is the current
  page and must omit href; it renders as inert text with aria-current="page"
  instead of a link. Earlier entries with href render real <a href>; the
  consumer supplies actual app routes.
- maxItems?: number (default 0 = never collapse). When maxItems > 0 and
  items.length > maxItems, collapse to: first item + an ellipsis + the last
  (maxItems - 1) items.
- separator?: React.ReactNode (default a lucide ChevronRight, size-3.5),
  rendered between every visible crumb and right after the ellipsis.

Behavior
- Semantic structure follows the WAI-ARIA breadcrumb pattern: <nav
  aria-label="Breadcrumb"> wrapping a single <ol>; each crumb and each
  separator is its own <li>. Separator <li>s carry aria-hidden="true" and
  role="presentation" so screen readers announce only the crumb labels.
- The collapsed ellipsis is a real focusable <button type="button"
  aria-label="Show hidden items"> — never decorative text. Clicking it
  flips one internal boolean (expanded) to true, re-rendering the full,
  uncollapsed trail. This is a one-way disclosure: there's no control to
  re-collapse, matching how most breadcrumb overflow menus behave.
- No custom keyboard handling is needed beyond the browser's native tab
  order — crumbs are plain anchors/button, so Tab/Shift+Tab and Enter/Space
  already work.

Rendering & styling
- Semantic tokens only. Container text-sm. Links: text-muted-foreground,
  hover:text-foreground, transition-colors. Current page: text-foreground
  font-medium, no hover state (it isn't interactive). Ellipsis button:
  text-muted-foreground, hover:bg-muted hover:text-foreground,
  focus-visible:ring-2 ring-ring, sized as a small square (size-6) so it
  reads as a control, not stray punctuation.
- The <ol> is flex flex-wrap items-center gap-1.5 so a long trail wraps to a
  second line instead of overflowing — collapsing via maxItems is the
  preferred fix for width, wrapping is just the safety net.
- No motion in this component; nothing here is decorative animation, so
  there's no prefers-reduced-motion branch to write.

Customization levers
- Separator: swap the ChevronRight for a Slash, a dot, or plain text like
  "/" — anything passed via the separator prop, no internal changes needed.
- Long last-segment truncation: add max-w-[Npx] truncate to the
  aria-current="page" span for trails whose final label can be arbitrarily
  long (file names, product titles).
- Structured data: because labels stay plain strings, a page can walk the
  same items array to emit a BreadcrumbList JSON-LD <script> in the page
  head for SEO — that's a page-level concern, not something the component
  does itself.
- Router integration: the default renders plain <a href>; swap that branch
  for next/link's Link (or another router's Link) to get client-side
  transitions and active-route awareness, without touching the collapse or
  accessibility logic.
- Collapse threshold: maxItems is the only sizing knob — raise it for wide
  headers, lower it (e.g. 3) for narrow sidebars or mobile toolbars.

Concepts

  • APG breadcrumb patternnav[aria-label="Breadcrumb"] wraps a single ol; the current page is inert text with aria-current="page", never a link, so assistive tech doesn't offer to "navigate" to where the user already is.
  • Collapsed overflow ellipsis — a real, focusable button rather than decorative "…" text: it has an accessible name ("Show hidden items") and reveals the hidden middle segments on demand instead of silently dropping them.
  • One-way disclosure — expanding is a one-time state flip with no path back to collapsed; this mirrors how most breadcrumb overflow menus behave and keeps the interaction model simple.
  • Presentational separators — chevrons (or any custom node) live in their own aria-hidden list items, so screen readers announce a clean sequence of crumb names with no stray glyphs between them.
  • Consumer-owned links — crumbs render plain <a href>; there's no internal router dependency, so swapping in next/link's Link or another router's link component is a same-shape edit, not a rewrite.

On This Page