{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "breadcrumb",
  "type": "registry:ui",
  "title": "Breadcrumb",
  "description": "An accessible React breadcrumb for Next.js and Tailwind CSS: a trail of links whose levels animate as you move, with custom separators and a collapsed middle.",
  "author": "FewTone <https://fewtone.com>",
  "dependencies": [
    "lucide-react@^1.52.0",
    "motion@^14.0.0",
    "radix-ui@^1.7.0"
  ],
  "registryDependencies": [
    "https://ui.fewtone.com/r/tokens.json",
    "https://ui.fewtone.com/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/fewtone/ui/breadcrumb.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { blur, duration, ease, spring } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\nimport { ChevronRightIcon, MoreHorizontalIcon } from \"lucide-react\";\nimport {\n  AnimatePresence,\n  type HTMLMotionProps,\n  LayoutGroup,\n  motion,\n  PresenceContext,\n  useReducedMotion,\n} from \"motion/react\";\nimport { Slot } from \"radix-ui\";\nimport { Children, type ComponentProps, type ReactNode, useMemo } from \"react\";\n\n/*\n * Breadcrumb (owner's roadmap, 2026-10-16: \"Where you are in the site, as a trail of links\"), with shadcn's parts:\n * Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator and\n * BreadcrumbEllipsis. The WAI-ARIA breadcrumb: a nav named \"Breadcrumb\" holding an ordered list, with the\n * current page marked aria-current=\"page\".\n * - The trail follows you (owner, 2026-10-19: \"see if we can do anything with motion\", then \"coming from left,\n *   more smoother\"): when it changes (your app moves to another page and keeps the breadcrumb), a new level grows\n *   out of the one before it, sliding 8px out from the left through the blur on duration.slow, the separator\n *   first and the name a beat later. A level you leave slides back into the one before it. The rest glide to\n *   their new places (layout=\"position\" on spring.snappy), and the level you were on dims to a link as smoothly\n *   as the new one arrives. Give each level a key when you map over a path; static trails are keyed by position\n *   and never move. Nothing animates on first paint (AnimatePresence initial={false}), and with reduced motion\n *   every change is instant.\n * - Links are muted and brighten on hover while their underline fades in (Button's link: the line's colour\n *   moves from transparent, so nothing shifts). A gliding highlight was tried instead and the owner kept the\n *   underline (2026-10-19). The current page is plain foreground text, its colour set on its level.\n * - Separators are hidden from screen readers (the list already says how many levels there are), and the\n *   chevron flips in right-to-left pages.\n * - BreadcrumbEllipsis stands in for levels that are collapsed; put it in a Dropdown Menu's trigger to list them\n *   (as the collapsed trail in the demo does).\n */\n\nfunction Breadcrumb({ \"aria-label\": label = \"Breadcrumb\", ...props }: ComponentProps<\"nav\">) {\n  return <nav aria-label={label} data-slot=\"breadcrumb\" {...props} />;\n}\n\n/** The ordered list of levels */\nfunction BreadcrumbList({ className, children, ...props }: ComponentProps<\"ol\">) {\n  // Keyed, so AnimatePresence can tell the levels apart: by your keys when you map over a path, by position\n  // otherwise (a static trail never changes, so nothing moves)\n  const levels = useMemo(() => Children.toArray(children), [children]);\n\n  return (\n    <ol\n      data-slot=\"breadcrumb-list\"\n      className={cn(\n        \"flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground sm:gap-2\",\n        className,\n      )}\n      {...props}\n    >\n      {/* The group re-renders the levels once a leaving one has gone, so the rest glide to their places */}\n      <LayoutGroup>\n        <AnimatePresence initial={false}>{levels}</AnimatePresence>\n      </LayoutGroup>\n    </ol>\n  );\n}\n\n/**\n * How a level comes, goes and moves. A new level grows out of the one before it: it slides 8px out from the left\n * through the blur, and a level you leave slides back in. `follows` holds a level's name back a beat\n * (duration.instant) behind the separator that leads to it, so the trail draws itself left to right. The rest\n * glide to their places when others change.\n */\nfunction useLevelMotion(follows: boolean) {\n  const reduce = useReducedMotion() ?? false;\n  const tucked = { opacity: 0, filter: reduce ? blur.shown : blur.hidden, x: reduce ? 0 : -8 };\n  return {\n    initial: tucked,\n    animate: { opacity: 1, filter: blur.shown, x: 0, transitionEnd: { filter: \"none\" } },\n    exit: { ...tucked, transition: reduce ? { duration: 0 } : { duration: duration.fast, ease: ease.in } },\n    transition: reduce\n      ? { duration: 0 }\n      : {\n          duration: duration.slow,\n          ease: ease.out,\n          delay: follows ? duration.instant : 0,\n          layout: spring.snappy,\n        },\n    layout: \"position\" as const,\n  };\n}\n\nexport type BreadcrumbItemProps = Omit<HTMLMotionProps<\"li\">, \"children\"> & { children?: ReactNode };\n\nfunction BreadcrumbItem({ className, children, ...props }: BreadcrumbItemProps) {\n  const level = useLevelMotion(true);\n  return (\n    <motion.li\n      data-slot=\"breadcrumb-item\"\n      className={cn(\n        \"inline-flex items-center gap-1.5\",\n        // The current page's colour lives here, so when the page you were on becomes a link it dims as smoothly\n        // as the next level arrives (and brightens the same way going back)\n        \"transition-colors duration-(--duration-slow) ease-out has-data-[slot=breadcrumb-page]:text-foreground\",\n        className,\n      )}\n      {...level}\n      {...props}\n    >\n      {/*\n       * The level answers to the trail's AnimatePresence; what's inside it doesn't. Otherwise a level that was\n       * there at first would stop anything in it from ever animating in (a menu's highlight, for example).\n       */}\n      <PresenceContext.Provider value={null}>{children}</PresenceContext.Provider>\n    </motion.li>\n  );\n}\n\nexport type BreadcrumbLinkProps = ComponentProps<\"a\"> & {\n  /** Render the single child (your router's Link) with the link's styles */\n  asChild?: boolean;\n};\n\nfunction BreadcrumbLink({ asChild = false, className, ...props }: BreadcrumbLinkProps) {\n  const Comp = asChild ? Slot.Root : \"a\";\n  return (\n    <Comp\n      data-slot=\"breadcrumb-link\"\n      className={cn(\n        \"rounded-md underline decoration-transparent underline-offset-4\",\n        \"transition-colors duration-(--duration-fast) ease-out hover:text-foreground hover:decoration-current\",\n        \"outline-ring focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-offset-(--focus-ring-offset)\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * The page you're on: the last item, as text rather than a link to itself. aria-current tells screen readers\n * it's the current page (shadcn's fake role=\"link\" is left out: it can't be focused or followed). It takes the\n * foreground colour from its BreadcrumbItem, which fades it in and out.\n */\nfunction BreadcrumbPage({ className, ...props }: ComponentProps<\"span\">) {\n  return <span data-slot=\"breadcrumb-page\" aria-current=\"page\" className={className} {...props} />;\n}\n\nexport type BreadcrumbSeparatorProps = Omit<HTMLMotionProps<\"li\">, \"children\"> & { children?: ReactNode };\n\n/** The mark between two items; a chevron unless you pass your own (a slash, for example) */\nfunction BreadcrumbSeparator({ children, className, ...props }: BreadcrumbSeparatorProps) {\n  const level = useLevelMotion(false);\n  return (\n    <motion.li\n      data-slot=\"breadcrumb-separator\"\n      role=\"presentation\"\n      aria-hidden=\"true\"\n      className={cn(\"[&>svg]:size-(--size-icon-sm) [&>svg]:rtl:rotate-180\", className)}\n      {...level}\n      {...props}\n    >\n      {children ?? <ChevronRightIcon />}\n    </motion.li>\n  );\n}\n\n/** Collapsed levels. Inside a Dropdown Menu's trigger, name the trigger (\"Show hidden levels\"). */\nfunction BreadcrumbEllipsis({ className, ...props }: ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"breadcrumb-ellipsis\"\n      className={cn(\"flex size-6 items-center justify-center\", className)}\n      {...props}\n    >\n      <MoreHorizontalIcon aria-hidden className=\"size-(--size-icon-md)\" />\n      <span className=\"sr-only\">More</span>\n    </span>\n  );\n}\n\nexport {\n  Breadcrumb,\n  BreadcrumbEllipsis,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n};\n"
    }
  ],
  "categories": [
    "navigation"
  ],
  "docs": "Usage and examples: https://ui.fewtone.com/components/breadcrumb"
}
