Breadcrumb
Shows where the current page sits in the hierarchy and gives one-click access to every level above it.
import Link from "next/link"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
export default function BreadcrumbDemo() {
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="/docs" />}>Docs</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="/docs/components" />}>
Components
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}Anatomy
- 1LinkAn ancestor level. Uses the destination page's title.
- 2SeparatorA quiet slash by default; hidden from assistive technology.
- 3Current pageThe last item. Not a link, marked with
aria-current="page".
Installation
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/breadcrumb.jsonUsage
import Link from "next/link"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="/docs" />}>Docs</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>BreadcrumbLink renders an <a> by default; pass your router's link through render to keep client-side navigation. The last item is a BreadcrumbPage — the current location, which is not a link.
Examples
Default
prfct separates levels with a light slash. It reads as a path, takes less space than a chevron, and stays quiet next to the links.
import Link from "next/link"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
export default function BreadcrumbDemo() {
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="/docs" />}>Docs</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="/docs/components" />}>
Components
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}Custom separator
Pass any icon as the child of BreadcrumbSeparator. Chevrons suit settings and file hierarchies where the trail represents drilling down.
import { ChevronRightIcon } from "lucide-react"
import Link from "next/link"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
export default function BreadcrumbCustomSeparator() {
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="/docs" />}>
Settings
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator>
<ChevronRightIcon />
</BreadcrumbSeparator>
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="/docs" />}>
Workspace
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator>
<ChevronRightIcon />
</BreadcrumbSeparator>
<BreadcrumbItem>
<BreadcrumbPage>Members</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}Collapsed
When a trail has more than four levels, keep the root and the last two, and collapse the middle into a Dropdown Menu behind an ellipsis. Nothing becomes unreachable.
import Link from "next/link"
import {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
const hidden = [
{ title: "Engineering", href: "/docs" },
{ title: "Platform", href: "/docs" },
{ title: "Design system", href: "/docs" },
]
export default function BreadcrumbCollapsed() {
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="/docs" />}>Acme</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger
aria-label="Show hidden path"
className="rounded-md text-muted-foreground focus-ring transition-colors duration-fast hover:bg-accent hover:text-foreground data-popup-open:bg-accent"
>
<BreadcrumbEllipsis />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-44">
<DropdownMenuGroup>
{hidden.map((item) => (
<DropdownMenuItem
key={item.title}
render={<Link href={item.href} />}
>
{item.title}
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="/docs" />}>
Components
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}With icons
A house icon can stand in for the root when the product name is already visible in the header — give it an aria-label. Icons in the middle of a trail should mark the type of a level, not decorate it.
import { FolderIcon, HouseIcon } from "lucide-react"
import Link from "next/link"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
export default function BreadcrumbWithIcon() {
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink
render={<Link href="/" />}
aria-label="Home"
className="flex items-center"
>
<HouseIcon className="size-4" />
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink
render={<Link href="/docs" />}
className="flex items-center gap-1.5"
>
<FolderIcon className="size-4" />
Projects
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Marketing site</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}Switcher
In apps, the last segment can double as a switcher between siblings — projects in a team, branches in a repo. The trigger keeps the look of the current page; the menu marks the active item.
"use client"
import * as React from "react"
import { ChevronsUpDownIcon } from "lucide-react"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
const projects = ["Marketing site", "Mobile app", "Design system"]
export default function BreadcrumbSwitcher() {
const [current, setCurrent] = React.useState("Design system")
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink
render={<Link href="/docs" />}
className="font-medium text-foreground"
>
Acme
</BreadcrumbLink>
<Badge variant="secondary" size="sm">
Pro
</Badge>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1.5 rounded-md px-1.5 py-1 font-medium text-foreground focus-ring transition-colors duration-fast hover:bg-accent data-popup-open:bg-accent">
<span aria-current="page">{current}</span>
<ChevronsUpDownIcon className="size-3.5 text-muted-foreground" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-52">
<DropdownMenuGroup>
<DropdownMenuLabel>Projects</DropdownMenuLabel>
<DropdownMenuRadioGroup
value={current}
onValueChange={setCurrent}
>
{projects.map((project) => (
<DropdownMenuRadioItem key={project} value={project}>
{project}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)
}Guidelines
When to use
- In hierarchies three or more levels deep — docs, file systems, settings, e-commerce categories.
- When people land deep in a structure from search or a shared link and need to orient themselves.
When not to use
- In flat sites with one or two levels — the page title is enough.
- To show a sequence of steps or history — breadcrumbs describe location, not the path someone took. Use a stepper for multi-step flows.
- As the primary navigation. Breadcrumbs complement a Sidebar or Navigation Menu; they don't replace them.
Content
Each item uses the destination page's title, shortened only if needed. Don't link the current page, don't repeat it right below as the page title in a different wording, and don't add a trailing separator.
Placement
Put the breadcrumb above the page title, left-aligned with it, in the page's content column. On narrow screens, collapse to the root and the parent level, or show only a Back to parent link.
Accessibility
- Landmark.
Breadcrumbrenders a<nav>labelled Breadcrumb (translated by LocaleProvider), so screen reader users can jump to it from the landmarks list. - Order. Items are an ordered list (
<ol>), which conveys the hierarchy and the number of levels. - Current page.
BreadcrumbPagerenders a<span>witharia-current="page", which marks it as the current location. It is plain text, not a link, so it isn't focusable. - Separators are
aria-hidden, so they aren't announced between levels. - Collapsed levels need a trigger with an accessible name, such as Show hidden path.
| Key | Behavior |
|---|---|
Tab | Moves focus through the links in order. The current page is skipped. |
Enter | Follows the focused link. |
API reference
Breadcrumb
The <nav> landmark. Accepts all <nav> props; override aria-label when a page has more than one breadcrumb.
BreadcrumbList and BreadcrumbItem
BreadcrumbList is the <ol> that lays out the trail and wraps on narrow screens. BreadcrumbItem is an <li> that aligns its content — a link, a page, or a menu trigger.
BreadcrumbLink
BreadcrumbPage
The current location. Renders a <span> with aria-current="page".
BreadcrumbSeparator
BreadcrumbEllipsis
A ⋯ glyph for collapsed levels, announced as More in the language set by LocaleProvider. Inside a trigger, give the trigger a name of its own that says what it reveals.