{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "breadcrumb",
  "type": "registry:ui",
  "title": "Breadcrumb",
  "description": "Show where a page sits in a hierarchy.",
  "categories": [
    "disclosure"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://mcp.xzero.co.uk/r/xzero-motion-tokens.json"
  ],
  "files": [
    {
      "path": "registry/components/breadcrumb/breadcrumb.tsx",
      "type": "registry:component",
      "target": "@components/xzero/breadcrumb/breadcrumb.tsx",
      "content": "\"use client\";\nimport type { MouseEvent } from \"react\";\nimport { ChevronRight as NavArrowRight } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { XzeroLink } from \"../lib/xzero-provider\";\nimport { motionTokens } from \"../lib/motion-tokens\";\nimport styles from \"./breadcrumb.module.css\";\nexport interface BreadcrumbItem {\n  label: string;\n  href?: string;\n  /** Runs when the crumb is chosen. Without an href the crumb renders as a button, for paths that live in local state. */\n  onClick?: (event: MouseEvent<HTMLElement>) => void;\n}\nexport interface BreadcrumbProps { items: BreadcrumbItem[]; ariaLabel?: string }\n/** Crumbs present on first render stay still; crumbs added later slide in from the path before them. */\nexport function Breadcrumb({ items, ariaLabel = \"Breadcrumb\" }: BreadcrumbProps) {\n  const reduced = useReducedMotion() ?? false;\n  const still = { duration: 0 };\n  const path = items.map(item => item.label).join(\"/\");\n  return <nav aria-label={ariaLabel}><ol className={styles.list}><AnimatePresence mode=\"popLayout\" initial={false}>{items.map((item, index) => {\n    const current = index === items.length - 1;\n    return <motion.li key={`${item.label}-${index}`}\n      layout={reduced ? false : \"position\"} layoutDependency={path}\n      initial={reduced ? false : { opacity: 0, x: -8, filter: `blur(${motionTokens.blur.subtle}px)` }} animate={{ opacity: 1, x: 0, filter: \"blur(0px)\" }}\n      exit={reduced ? { opacity: 0, transition: still } : { opacity: 0, x: -4, filter: `blur(${motionTokens.blur.subtle}px)`, transition: { duration: motionTokens.duration.instant, ease: [...motionTokens.ease.standard] } }}\n      transition={reduced ? still : { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter], layout: motionTokens.spring.smooth }}>\n      {index > 0 && <NavArrowRight width={14} height={14} aria-hidden=\"true\"/>}\n      {!current && item.href ? <XzeroLink href={item.href} data-label={item.label} onClick={item.onClick}>{item.label}</XzeroLink>\n        : !current && item.onClick ? <button type=\"button\" data-label={item.label} onClick={item.onClick}>{item.label}</button>\n        : <span aria-current={current ? \"page\" : undefined} data-label={item.label}>{item.label}</span>}\n    </motion.li>;\n  })}</AnimatePresence></ol></nav>;\n}\n"
    },
    {
      "path": "registry/components/breadcrumb/breadcrumb.module.css",
      "type": "registry:component",
      "target": "@components/xzero/breadcrumb/breadcrumb.module.css",
      "content": "/* The gap between crumbs matches the gap inside one, so every chevron sits centered between its two labels. */\n.list { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; margin: 0; padding: 0; list-style: none; }\n.list li { display: inline-flex; align-items: center; gap: 6px; color: var(--text-muted); font-size: var(--text-sm); line-height: var(--leading-body); white-space: nowrap; }\n.list li > svg { width: 16px; height: 16px; flex: 0 0 auto; color: var(--border-strong); stroke-width: 1.75; }\n/* Each crumb reserves the width of its medium weight label, so becoming the current page never shifts the path. */\n.list a, .list li > span, .list li > button { display: inline-flex; flex-direction: column; border-radius: 7px; padding: 4px 2px; }\n.list a::after, .list li > span::after, .list li > button::after { height: 0; overflow: hidden; content: attr(data-label); font-weight: 500; visibility: hidden; user-select: none; pointer-events: none; }\n.list li > button { border: 0; margin: 0; background: none; font: inherit; line-height: inherit; text-align: start; cursor: pointer; }\n.list a, .list li > button { color: var(--text-secondary); text-decoration: underline 1px; text-decoration-color: transparent; text-underline-offset: 4px; transition: color var(--duration-fast) var(--ease-standard), text-decoration-color var(--duration-fast) var(--ease-standard); }\n.list [aria-current=\"page\"] { color: var(--accent-strong); font-weight: 500; transition: color var(--duration-fast) var(--ease-standard); }\n@media (hover: hover) and (pointer: fine) { .list a:hover, .list li > button:hover { color: var(--accent-strong); text-decoration-color: color-mix(in oklab, currentColor 45%, transparent); } }\n@media (prefers-reduced-motion: reduce) { .list a, .list li > button, .list [aria-current=\"page\"] { transition: none; } }\n"
    },
    {
      "path": "lib/xzero-provider.tsx",
      "type": "registry:lib",
      "target": "@components/xzero/lib/xzero-provider.tsx",
      "content": "\"use client\";\n\n/**\n * Lets an app swap the link and image elements Xzero components render (kuratlielia/arc-library#13).\n *\n * Without a provider, Xzero renders a plain `<a>` and `<img>`, so components work in any React app (Vite, Remix, Astro,\n * plain React). Wrap the app once to use your framework's or your own components instead:\n *\n *   import Link from \"next/link\";\n *   import Image from \"next/image\";\n *   import { XzeroProvider } from \"@/components/xzero/lib/xzero-provider\";\n *\n *   <XzeroProvider link={Link} image={Image}>{children}</XzeroProvider>\n *\n * Any component that accepts these props works, such as a locale-aware Link from next-intl or a React Router Link\n * wrapped to map `href` to `to`.\n */\nimport { createContext, createElement, useContext, useMemo, type AnchorHTMLAttributes, type ComponentType, type ImgHTMLAttributes, type ReactNode, type Ref } from \"react\";\n\n/** The props Xzero passes to a link: an href plus ordinary anchor attributes. */\nexport interface XzeroLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {\n  href: string;\n  ref?: Ref<HTMLAnchorElement>;\n}\n\n/**\n * The props Xzero passes to an image. A subset of next/image's props, so next/image can be passed as is: either `fill`\n * (cover the positioned parent) or `width` and `height`, plus `sizes` and `priority` as hints.\n */\nexport interface XzeroImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, \"src\" | \"alt\" | \"width\" | \"height\" | \"loading\" | \"srcSet\"> {\n  src: string;\n  alt: string;\n  width?: number;\n  height?: number;\n  /** Fills the nearest positioned parent, like next/image's `fill`. */\n  fill?: boolean;\n  /** Loads the image eagerly with high priority, like next/image's `priority`. */\n  priority?: boolean;\n  loading?: \"eager\" | \"lazy\";\n  ref?: Ref<HTMLImageElement>;\n}\n\nexport type XzeroLinkComponent = ComponentType<XzeroLinkProps>;\nexport type XzeroImageComponent = ComponentType<XzeroImageProps>;\n\n/** The default link: a plain anchor. */\nexport function XzeroAnchor(props: XzeroLinkProps) {\n  return <a {...props} />;\n}\n\nconst fillStyle = { position: \"absolute\", inset: 0, width: \"100%\", height: \"100%\" } as const;\n\n/** The default image: a plain, lazily decoded `<img>` that understands `fill` and `priority`. */\nexport function XzeroImg({ alt, fill, priority, loading, style, ...props }: XzeroImageProps) {\n  // eslint-disable-next-line @next/next/no-img-element -- the framework-free default; pass next/image through XzeroProvider instead.\n  return <img alt={alt} decoding=\"async\" {...props} loading={loading ?? (priority ? \"eager\" : \"lazy\")} fetchPriority={priority ? \"high\" : props.fetchPriority} style={fill ? { ...fillStyle, ...style } : style} />;\n}\n\ninterface XzeroComponents {\n  link: XzeroLinkComponent;\n  image: XzeroImageComponent;\n}\n\nconst XzeroContext = createContext<XzeroComponents>({ link: XzeroAnchor, image: XzeroImg });\n\nexport interface XzeroProviderProps {\n  /** The link component Xzero renders for navigation, such as next/link or your own wrapped Link. Defaults to `<a>`. */\n  link?: XzeroLinkComponent;\n  /** The image component Xzero renders for photos, such as next/image. Defaults to `<img>`. */\n  image?: XzeroImageComponent;\n  children: ReactNode;\n}\n\n/** Sets the link and image components for every Xzero component inside it. Nested providers inherit what they do not set. */\nexport function XzeroProvider({ link, image, children }: XzeroProviderProps) {\n  const parent = useContext(XzeroContext);\n  const value = useMemo(() => ({ link: link ?? parent.link, image: image ?? parent.image }), [link, image, parent]);\n  return <XzeroContext.Provider value={value}>{children}</XzeroContext.Provider>;\n}\n\n/** The link component from the nearest XzeroProvider, or a plain anchor. */\nexport const useArcLink = () => useContext(XzeroContext).link;\n/** The image component from the nearest XzeroProvider, or a plain img. */\nexport const useArcImage = () => useContext(XzeroContext).image;\n\n/** What Xzero components render for a link: the provider's link component, or a plain anchor. */\nexport function XzeroLink(props: XzeroLinkProps) {\n  return createElement(useArcLink(), props);\n}\n\n/** What Xzero components render for an image: the provider's image component, or a plain img. */\nexport function XzeroImage(props: XzeroImageProps) {\n  return createElement(useArcImage(), props);\n}\n"
    }
  ],
  "docs": "Docs and live preview: https://ui.xzero.co.uk/components/breadcrumb",
  "meta": {
    "tier": "free",
    "kind": "component",
    "tags": [
      "navigation",
      "wayfinding"
    ],
    "docs": "https://ui.xzero.co.uk/components/breadcrumb",
    "markdown": "https://ui.xzero.co.uk/components/breadcrumb/markdown"
  }
}