{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"breadcrumb","type":"registry:component","title":"Breadcrumb","description":"Where you are and the way back: a long trail folds its middle into \"…\", which opens the hidden crumbs in place one after another.","author":"Hairline UI","dependencies":[],"registryDependencies":["https://hairlineui.com/r/ui-interaction-motion.json?as=dependency"],"files":[{"path":"registry/breadcrumb.tsx","type":"registry:component","content":"// Hairline UI · breadcrumb · MIT License\n// Copyright (c) 2026 Hairline UI · https://hairlineui.com/license#components\n\n\"use client\";\n\nimport * as React from \"react\";\nimport { EASE_OUT, INTERACTION_FOCUS, useInteractionReducedMotion } from \"./ui-interaction-motion\";\n\n/* ─────────────────────────────────────────────────────────\n * BREADCRUMB: where you are, and the way back\n *\n *   long     past `max` crumbs (or on a narrow screen) the middle\n *            folds into \"…\"; pressing it opens them in place, each\n *            crumb's width easing open with a short stagger\n *   current  the last crumb is the page you're on: not a link,\n *            aria-current=\"page\"\n * ───────────────────────────────────────────────────────── */\n\nexport interface Crumb {\n  label: string;\n  href?: string;\n  icon?: React.ReactNode;\n  onClick?: () => void;\n}\n\nexport interface BreadcrumbProps {\n  items: Crumb[];\n  /** Crumbs shown before the middle folds; the first and the last two stay. */\n  max?: number;\n  className?: string;\n}\n\nfunction Slash() {\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className=\"size-3.5 shrink-0 text-muted-foreground/60\">\n      <path d=\"M10 3.5 6 12.5\" stroke=\"currentColor\" strokeWidth=\"1.3\" strokeLinecap=\"round\" />\n    </svg>\n  );\n}\n\nexport function Breadcrumb({ items, max = 4, className = \"\" }: BreadcrumbProps) {\n  const reduced = useInteractionReducedMotion();\n  const [expanded, setExpanded] = React.useState(false);\n  const fold = items.length > max && !expanded;\n  // Folded crumbs: everything between the first and the last two.\n  const hiddenFrom = 1;\n  const hiddenTo = items.length - 2;\n\n  return (\n    <nav aria-label=\"Breadcrumb\" className={className}>\n      <ol className=\"flex min-w-0 flex-wrap items-center gap-y-1 text-[13px]\">\n        {items.map((item, i) => {\n          const last = i === items.length - 1;\n          const folds = items.length > max && i >= hiddenFrom && i < hiddenTo;\n          const crumbClass = `inline-flex items-center gap-1.5 whitespace-nowrap rounded-md [&_svg]:size-3.5 ${INTERACTION_FOCUS}`;\n          const body = (\n            <>\n              {item.icon}\n              {item.label}\n            </>\n          );\n          const node = last ? (\n            <span aria-current=\"page\" className={`${crumbClass} font-medium text-foreground`}>\n              {body}\n            </span>\n          ) : item.href ? (\n            <a href={item.href} onClick={item.onClick} className={`${crumbClass} text-muted-foreground transition-colors duration-200 hover:text-foreground`}>\n              {body}\n            </a>\n          ) : (\n            <button type=\"button\" onClick={item.onClick} className={`${crumbClass} text-muted-foreground cursor-pointer transition-colors duration-200 hover:text-foreground`}>\n              {body}\n            </button>\n          );\n          return (\n            <React.Fragment key={`${item.label}-${i}`}>\n              {folds && i === hiddenFrom && (\n                <li\n                  className=\"grid\"\n                  style={{ gridTemplateColumns: fold ? \"1fr\" : \"0fr\", opacity: fold ? 1 : 0, transition: reduced ? \"none\" : `grid-template-columns 320ms ${EASE_OUT}, opacity 160ms ${EASE_OUT}` }}\n                  aria-hidden={!fold || undefined}\n                >\n                  <span className=\"flex min-w-0 items-center overflow-hidden\">\n                    <button\n                      type=\"button\"\n                      tabIndex={fold ? 0 : -1}\n                      aria-label={`Show ${hiddenTo - hiddenFrom} more`}\n                      onClick={() => setExpanded(true)}\n                      className={`mx-0.5 inline-flex h-6 items-center rounded-full px-1.5 text-muted-foreground cursor-pointer hover:bg-accent hover:text-foreground ${INTERACTION_FOCUS}`}\n                    >\n                      …\n                    </button>\n                    <Slash />\n                  </span>\n                </li>\n              )}\n              <li\n                className={folds ? \"grid\" : \"flex items-center\"}\n                aria-hidden={(folds && fold) || undefined}\n                style={\n                  folds\n                    ? {\n                        gridTemplateColumns: fold ? \"0fr\" : \"1fr\",\n                        opacity: fold ? 0 : 1,\n                        transition: reduced ? \"none\" : `grid-template-columns 380ms ${EASE_OUT} ${(i - hiddenFrom) * 40}ms, opacity 260ms ${EASE_OUT} ${(i - hiddenFrom) * 40 + 80}ms`,\n                      }\n                    : undefined\n                }\n              >\n                <span className={`flex min-w-0 items-center ${folds ? \"overflow-hidden\" : \"\"}`} inert={(folds && fold) || undefined}>\n                  {node}\n                  {!last && <Slash />}\n                </span>\n              </li>\n            </React.Fragment>\n          );\n        })}\n      </ol>\n    </nav>\n  );\n}\n\nexport default Breadcrumb;\n"}],"css":{},"docs":"Docs and live preview: https://hairlineui.com/ui/breadcrumb. MIT licensed: https://hairlineui.com/license#components","meta":{"license":"MIT"}}