Breadcrumb
An accessible trail of links to the current page, with aria-current on the active crumb and swappable separators.
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
export default function BreadcrumbDemo() {
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/docs">Docs</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Registry</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}Installation
Run the following command
npx shadcn@latest add @ai2/breadcrumbDependencies, the @ai2/tokens theme and the component file are installed together.
Install dependencies
npm install radix-ui@^1.6.1 lucide-react@^1.23.0Add the cn util
lib/utils.tsimport { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
/* Adds a source-attribution ref param to a URL (the inspiration exports mark their
outbound links with an ai2.design attribution). An invalid URL is returned as is.
This file is SHOWN TO THE CONSUMER: the docs component pages render the source of
`cn` in a code block, so a Turkish comment here would reach every one of those
pages. Keep it English. */
export function withRef(url: string, ref = "ai2.design"): string {
try {
const u = new URL(url)
u.searchParams.set("ref", ref)
return u.toString()
} catch {
return url
}
}Copy the source code
components/ui/breadcrumb.tsximport type * as React from "react"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import { Slot } from "@/components/ui/primitives"
import { cn } from "@/lib/utils"
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
return (
<ol
data-slot="breadcrumb-list"
className={cn(
"flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground sm:gap-2",
className
)}
{...props}
/>
)
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-item"
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
)
}
function BreadcrumbLink({
className,
asChild,
...props
}: React.ComponentProps<"a"> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "a"
return (
<Comp
data-slot="breadcrumb-link"
className={cn(
"rounded-sm outline-none transition-colors duration-(--motion-fast) hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50",
className
)}
{...props}
/>
)
}
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-medium text-foreground", className)}
{...props}
/>
)
}
function BreadcrumbSeparator({
children,
className,
...props
}: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
)
}
function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
className={cn("flex size-6 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="size-4" />
</span>
)
}
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
}Manual installs skip the @ai2/tokens theme, so add the token CSS from the theming guide or the tone colors will be missing.
Usage
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Settings</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>Use BreadcrumbLink for ancestors and BreadcrumbPage for the current page, which renders a non-interactive span with aria-current="page".
Examples
Custom separator
BreadcrumbSeparator renders a chevron by default; pass any icon as children to replace it.
Collapsed
Use BreadcrumbEllipsis to collapse long trails; it stays hidden from screen readers apart from an sr-only "More" label.
As child
Props
Every part renders a plain semantic element (nav, ol, li, a, span) and accepts its native props. The only extra prop is on BreadcrumbLink.
BreadcrumbLink props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merges props onto the child element (e.g. a router Link) instead of rendering a native anchor. |
ai2 Breadcrumb: accessible navigation trails for React
The ai2 Breadcrumb is a shadcn-compatible react breadcrumb component styled with Tailwind CSS v4. It renders the path to the current page as a semantic nav landmark with an ordered list inside, so users (and search engines) can see where they are and jump back up the hierarchy. Docs sites, dashboards, file browsers and e-commerce category pages are the classic homes for it.
It ships through the shadcn registry format, so you install it with one CLI command, an MCP agent, or a copy-paste, and the TypeScript source lands in your own project. You own the file; there is no runtime dependency on ai2 itself. The live example above is the exact component you get.
What is the ai2 Breadcrumb?
It is a seven-part composition matching shadcn/ui: Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator and BreadcrumbEllipsis. Each part renders a plain semantic element: nav, ol, li, a and span.
Accessibility is baked into the markup. The root nav carries aria-label="breadcrumb", the current page renders as a non-interactive span with aria-current="page", separators are hidden from assistive technology with aria-hidden and role="presentation", and the ellipsis exposes an sr-only "More" label. There is no radix primitive here because plain HTML semantics already do the job.
Why use it
- Correct a11y markup out of the box: aria-label="breadcrumb" on the nav, aria-current="page" on the active crumb and aria-hidden separators follow the WAI-ARIA breadcrumb pattern, so screen readers announce the trail correctly with zero configuration.
- Router-agnostic links: BreadcrumbLink renders a plain anchor by default and accepts asChild to merge onto a Next.js Link or any router's component, keeping client-side navigation intact.
- Swappable separators: The chevron default can be replaced per separator by passing children (a Slash, a dot, custom svg), and the change stays invisible to assistive technology.
- Built-in trail collapsing: BreadcrumbEllipsis gives long paths the standard collapsed middle without inventing your own overflow logic.
- Agent-readable metadata: The registry item describes the parts and intended use in plain words, so an MCP agent can find, inspect and install it without guessing.
Features
- Semantic nav plus ordered list: The trail is a real nav landmark wrapping an ol, so document outlines, crawlers and assistive technology all read the hierarchy natively.
- aria-current on the active crumb: BreadcrumbPage renders a non-interactive span with aria-current="page" and aria-disabled, clearly separating the current location from the links.
- Custom separators: BreadcrumbSeparator renders a ChevronRight by default; pass any icon as children to replace it, per separator or across the trail.
- Ellipsis for long trails: BreadcrumbEllipsis renders a horizontal-dots icon with an sr-only "More" label, hidden from screen readers as decoration.
- Focus-visible ring on links: Every BreadcrumbLink keeps the shared ai2 focus ring and a token-driven hover transition, matching interactive elements across the system.
- Data attributes for styling: Every part exposes data-slot (breadcrumb, breadcrumb-list, breadcrumb-link and so on) for targeted CSS overrides without forking.
Production tips
- Always end with BreadcrumbPage: The last crumb should be the current page as a span, not a link. A self-link confuses users and wastes the aria-current semantics the component provides.
- Use asChild with your router: In Next.js, wrap Link with BreadcrumbLink asChild so navigation stays client-side. A plain href causes a full page load.
- Collapse from the middle: Keep the first crumb (Home or the root) and the last one or two levels visible, and put BreadcrumbEllipsis between them. The edges carry the most information.
- Keep labels short: Crumbs are wayfinding, not titles. Truncate long entity names with a max-width and text-ellipsis on the specific crumb rather than letting the trail wrap twice.
- One breadcrumb per page: The nav is labeled "breadcrumb"; two of them create duplicate landmarks. If sub-areas need their own trail, merge it into the single top trail instead.
Works with the rest of ai2
Breadcrumbs usually sit in the page header next to an ai2 Avatar or workspace switcher, with an ai2 Badge after the current crumb for statuses like Draft. A ai2 Dropdown Menu can hang off the ellipsis to expose the collapsed levels as a menu.
For deep drill-downs, pair the trail with ai2 Pagination at the bottom of list pages, and use an ai2 Separator to divide the header from content. Everything shares one token source, so the muted trail matches the rest of your chrome in both modes.