{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "breadcrumb",
  "type": "registry:ui",
  "title": "Breadcrumb",
  "description": "Where a page sits, with a slash between the levels and the current page in the foreground. Past four levels the middle folds into a round ghost button that opens the Menu, and phones show the last two levels. No motion of its own.",
  "registryDependencies": [
    "https://littmd.vercel.app/r/litt.json",
    "https://littmd.vercel.app/r/button.json",
    "https://littmd.vercel.app/r/icons.json",
    "https://littmd.vercel.app/r/menu.json",
    "https://littmd.vercel.app/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/litt/ui/breadcrumb.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n// @litt/breadcrumb 2026.11.06. What changed: https://littmd.vercel.app/changelog.md\n\n/**\n * Breadcrumb: where a page sits. Studio / Projects / Marlow Labs / Settings.\n *\n * - Anatomy: a nav landmark (named \"Breadcrumb\" by default) holding an ordered list on one line. Each level is a\n *   link at the small size in fg-muted that turns fg on hover, on the instant tier, color only, with no underline.\n *   The last level is the current page: fg, not a link, with aria-current=\"page\", and it truncates with an\n *   ellipsis instead of wrapping. Between levels a \"/\" in fg-subtle with --litt-space-2 each side, hidden from screen\n *   readers. No dots, no chevrons (a chevron reads as \"opens\"), no accent.\n * - Targets: a link's hit area is 32px tall, and 44px on coarse pointers. The extra height is an invisible layer\n *   around the link (::before), so the text never moves and the row keeps its height.\n * - Long trails: past maxVisible levels (four) the middle ones fold into an icon-only ghost sm \"…\" button (a pill)\n *   named \"Show 3 more levels\", which opens litt's Menu of them, in order. The first level and the last three\n *   stay. On compact screens (useDevice().compact) only the last two levels show and the rest fold.\n * - Router: links are plain <a> unless you pass renderLink, as the Header takes it (data items, so a function\n *   draws each link). The folded levels are real links in the Menu (MenuItem href) unless you pass onNavigate.\n * - Motion: none. The Menu that holds the folded levels owns its own, on the responsive tier.\n * - Flat: plain words with slashes between; a link takes a grey bubble on hover.\n */\nimport type { ReactNode } from \"react\";\nimport { useDevice } from \"../../lib/litt/motion\";\nimport { Button } from \"./button\";\nimport { MoreIcon } from \"./icons\";\nimport { Menu, MenuItem } from \"./menu\";\n\n/** One level of the trail. */\nexport type BreadcrumbItem = {\n  /** The level's name, as short as the page title allows. */\n  label: string;\n  /** Where the level goes. Also the key, so give each level its own. The last level is the current page and is not drawn as a link. */\n  href: string;\n};\n\n/** What renderLink receives to put on your link, so a router link looks exactly like the default. */\nexport type BreadcrumbLinkProps = {\n  /** The breadcrumb's link classes. Pass them to your link. */\n  className: string;\n  /** The label. Pass it as the link's children. */\n  children: ReactNode;\n};\n\nexport type BreadcrumbProps = {\n  /** The levels, outermost first, each `{ label, href }`. The last one is the current page. */\n  items: BreadcrumbItem[];\n  /** The nav landmark's name. Give each breadcrumb on a page its own. */\n  label?: string;\n  /** How many levels show before the middle ones fold into the \"…\" menu: the first level and the last ones. At least 2. Compact screens always show the last two. */\n  maxVisible?: number;\n  /** Draws each link. Receives the level and the props to pass on. Use it for a router link, for example `(item, props) => <Link href={item.href} {...props} />`. */\n  renderLink?: (item: BreadcrumbItem, props: BreadcrumbLinkProps) => ReactNode;\n  /** Pass your router's navigate to keep client-side routing in the menu of folded levels. Without it, its rows are plain links. */\n  onNavigate?: (item: BreadcrumbItem) => void;\n  /** Gap between the \"…\" button and its menu, in px. In a glass bar, make it reach past the bar's bottom edge, so the menu floats over the page and never over the bar's own glass. */\n  menuOffset?: number;\n  /** Extra classes for the nav, such as a max width. */\n  className?: string;\n};\n\nconst link =\n  \"relative inline-flex h-litt-control-sm shrink-0 items-center rounded-litt-control whitespace-nowrap text-litt-fg-muted \" +\n  \"[-webkit-tap-highlight-color:transparent] transition-colors duration-(--litt-dur-instant) ease-litt \" +\n  \"hover:text-litt-fg pointer-coarse:active:text-litt-fg \" +\n  /* Flat: plain words that light up as a grey bubble. */\n  \"in-[[data-material=flat]]:px-3 in-[[data-material=flat]]:hover:bg-litt-surface-3 \" +\n  /* 44px on a finger, without moving the text: the layer is centered on the 32px link and adds 6px above and below. */\n  \"pointer-coarse:before:absolute pointer-coarse:before:inset-x-0 pointer-coarse:before:-inset-y-1.5 \" +\n  \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-litt-focus\";\n\n/** The \"/\" between two levels. Decoration: a screen reader hears the list instead. */\nfunction Slash() {\n  return (\n    <span aria-hidden=\"true\" className=\"shrink-0 px-2 text-litt-fg-subtle select-none in-[[data-material=flat]]:px-0.5\">\n      /\n    </span>\n  );\n}\n\n/** Which levels show, and which fold into the \"…\" menu. */\nfunction fold(items: BreadcrumbItem[], maxVisible: number, compact: boolean) {\n  const keep = compact ? 2 : Math.max(2, Math.floor(maxVisible));\n  if (items.length <= keep) return { head: [], folded: [], tail: items };\n  /* On a phone the current page and its parent are what people need. Elsewhere the root stays too. */\n  const head = compact ? [] : items.slice(0, 1);\n  const tail = items.slice(items.length - (keep - head.length));\n  return { head, folded: items.slice(head.length, items.length - tail.length), tail };\n}\n\n/** Where a page sits, from the outermost level to the current page. Long trails fold their middle into a menu. */\nexport function Breadcrumb({\n  items,\n  label = \"Breadcrumb\",\n  maxVisible = 4,\n  renderLink,\n  onNavigate,\n  menuOffset = 8,\n  className = \"\",\n}: BreadcrumbProps) {\n  const { compact } = useDevice();\n  if (items.length === 0) return null;\n  const { head, folded, tail } = fold(items, maxVisible, compact);\n  const current = items[items.length - 1];\n  const draw = renderLink ?? ((item: BreadcrumbItem, props: BreadcrumbLinkProps) => <a href={item.href} {...props} />);\n  const level = (item: BreadcrumbItem) =>\n    item === current ? (\n      <span aria-current=\"page\" className=\"min-w-0 truncate text-litt-fg in-[[data-material=flat]]:px-3\">\n        {item.label}\n      </span>\n    ) : (\n      draw(item, { className: link, children: item.label })\n    );\n  const more = folded.length === 1 ? \"Show 1 more level\" : `Show ${folded.length} more levels`;\n\n  const rows: { key: string; node: ReactNode }[] = head.map((item) => ({ key: item.href, node: level(item) }));\n  if (folded.length > 0)\n    rows.push({\n      key: \"folded\",\n      node: (\n        <Menu\n          align=\"start\"\n          sideOffset={menuOffset}\n          trigger={\n            <Button\n              iconOnly\n              variant=\"ghost\"\n              size=\"sm\"\n              aria-label={more}\n              /* The round button has its own air around the dots, so it takes 4px of each slash's. And 44px to a\n                 finger, like the links, without growing the row. */\n              className=\"-mx-1 pointer-coarse:before:absolute pointer-coarse:before:-inset-1.5\"\n            >\n              <MoreIcon />\n            </Button>\n          }\n        >\n          {folded.map((item) => (\n            <MenuItem key={item.href} {...(onNavigate ? { onSelect: () => onNavigate(item) } : { href: item.href })}>\n              {item.label}\n            </MenuItem>\n          ))}\n        </Menu>\n      ),\n    });\n  for (const item of tail) rows.push({ key: item.href, node: level(item) });\n\n  return (\n    <nav aria-label={label} className={\"min-w-0 \" + className}>\n      <ol className=\"flex h-litt-control-sm min-w-0 items-center text-litt-small\">\n        {rows.map((row, i) => (\n          /* The current page's item may shrink, so it is the one that truncates. */\n          <li key={row.key} className={\"flex items-center \" + (i === rows.length - 1 ? \"min-w-0\" : \"shrink-0\")}>\n            {i > 0 && <Slash />}\n            {row.node}\n          </li>\n        ))}\n      </ol>\n    </nav>\n  );\n}\n",
      "target": "components/litt/breadcrumb.tsx"
    }
  ],
  "docs": "Import Breadcrumb from components/litt/breadcrumb. Pass items as { label, href }, the last one being the current page. Pass renderLink and onNavigate to route with your router, and maxVisible to change where the trail folds.",
  "meta": {
    "version": "2026.11.06",
    "released": "2026-11-06",
    "changelog": "https://littmd.vercel.app/changelog.md"
  }
}
