Breadcrumb
Hierarchical navigation showing the user's location within a site.
import { Breadcrumb } from '@/components/breadcrumb';
export default function BreadcrumbPreview() {
return (
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Components</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Breadcrumb</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
);
} Dependencies
Source Code
import { CaretRightIcon, DotsThreeIcon } from '@phosphor-icons/react/dist/ssr';
import { Slot } from '@/components/slot';
import { cn } from '@/lib/utils/classnames';
const Breadcrumb = ({
ref,
className,
...props
}: React.ComponentPropsWithRef<'nav'>) => {
return (
<nav
ref={ref}
aria-label="breadcrumb"
className={cn('text-sm', className)}
{...props}
/>
);
};
const BreadcrumbList = ({
ref,
className,
...props
}: React.ComponentPropsWithRef<'ol'>) => {
return (
<ol
ref={ref}
className={cn(
'wrap-break-word flex flex-wrap items-center gap-1.5 text-foreground-secondary sm:gap-2.5',
className
)}
{...props}
/>
);
};
const BreadcrumbItem = ({
ref,
className,
...props
}: React.ComponentPropsWithRef<'li'>) => {
return (
<li
ref={ref}
className={cn('inline-flex items-center gap-1.5', className)}
{...props}
/>
);
};
interface BreadcrumbLinkProps extends React.ComponentPropsWithRef<'a'> {
asChild?: boolean;
}
const BreadcrumbLink = ({
ref,
className,
asChild,
...props
}: BreadcrumbLinkProps) => {
const Comp = asChild ? Slot : 'a';
return (
<Comp
ref={ref}
className={cn(
'focus-visible:ring-(length:--ring-width) rounded-xs outline-none ring-ring transition hover:text-foreground',
className
)}
{...props}
/>
);
};
interface BreadcrumbPageProps extends React.ComponentPropsWithRef<'span'> {
asChild?: boolean;
}
const BreadcrumbPage = ({
ref,
className,
asChild,
...props
}: BreadcrumbPageProps) => {
const Comp = asChild ? Slot : 'span';
return (
<Comp
ref={ref}
aria-current="page"
className={cn('font-medium text-foreground', className)}
{...props}
/>
);
};
const BreadcrumbSeparator = ({
className,
children,
...props
}: React.ComponentPropsWithRef<'li'>) => {
return (
<li
role="presentation"
aria-hidden="true"
className={cn('inline-flex items-center [&>svg]:size-3.5', className)}
{...props}
>
{children ?? <CaretRightIcon />}
</li>
);
};
const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentPropsWithRef<'span'>) => {
return (
<span
role="presentation"
aria-hidden="true"
className={cn('flex size-5 items-center justify-center', className)}
{...props}
>
<DotsThreeIcon className="size-4" />
</span>
);
};
const CompoundBreadcrumb = Object.assign(Breadcrumb, {
List: BreadcrumbList,
Item: BreadcrumbItem,
Link: BreadcrumbLink,
Page: BreadcrumbPage,
Separator: BreadcrumbSeparator,
Ellipsis: BreadcrumbEllipsis,
});
export { CompoundBreadcrumb as Breadcrumb }; Anatomy
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link />
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Ellipsis />
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page />
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
API Reference
Breadcrumb
Extends the nav element. Renders with aria-label="breadcrumb".
Breadcrumb.List
Extends the ol element. Ordered list — the order is meaningful and read out by screen readers.
Breadcrumb.Item
Extends the li element.
Breadcrumb.Link
Extends the a element.
| Prop | Default | Type | Description |
|---|---|---|---|
asChild | - | boolean | Use to compose with a router-aware Link component. |
Breadcrumb.Page
Extends the span element. Represents the current page — not a navigable target. Renders with aria-current="page" per the WAI-ARIA Breadcrumb pattern.
| Prop | Default | Type |
|---|---|---|
asChild | - | boolean |
Breadcrumb.Separator
Extends the li element. Decorative — rendered with role="presentation" and aria-hidden="true". Defaults to a chevron icon. Pass children to override (e.g., /, >, or any element).
Breadcrumb.Ellipsis
Extends the span element. Decorative collapse indicator. Combine with a Menu (or any disclosure) to expand the hidden segments.
Examples
With an icon
Replace the first segment’s label with an icon (commonly a home glyph). Pair it with aria-label so the link still announces meaningfully.
import { HouseIcon } from '@phosphor-icons/react/dist/ssr';
import { Breadcrumb } from '@/components/breadcrumb';
export default function BreadcrumbIconPreview() {
return (
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link
href="#"
aria-label="Home"
className="inline-flex items-center"
>
<HouseIcon className="size-4" />
</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Components</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Breadcrumb</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
);
} Custom separator
import { Breadcrumb } from '@/components/breadcrumb';
export default function BreadcrumbCustomSeparatorPreview() {
return (
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Docs</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator>/</Breadcrumb.Separator>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">UI</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator>/</Breadcrumb.Separator>
<Breadcrumb.Item>
<Breadcrumb.Page>Breadcrumb</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
);
} Collapsed with dropdown
When the trail is too long, replace middle segments with Breadcrumb.Ellipsis inside a Menu so they remain reachable.
import { Breadcrumb } from '@/components/breadcrumb';
import { Menu } from '@/components/menu';
export default function BreadcrumbCollapsedPreview() {
return (
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Menu>
<Menu.Trigger asChild>
<button
type="button"
aria-label="More breadcrumbs"
className="cursor-pointer hover:text-foreground"
>
<Breadcrumb.Ellipsis />
</button>
</Menu.Trigger>
<Menu.Items>
<Menu.Item>Components</Menu.Item>
<Menu.Item>Navigation</Menu.Item>
<Menu.Item>Lists</Menu.Item>
</Menu.Items>
</Menu>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Breadcrumb</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Collapsed</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
);
} Best Practices
-
Usage:
- The last item should be
Breadcrumb.Page, notBreadcrumb.Link— it’s the current location. - Keep labels short. Truncate long names with
text-ellipsisand a fixedmax-w-*rather than wrapping mid-word.
- The last item should be
-
Accessibility:
- The
<nav aria-label="breadcrumb">and ordered<ol>are required for screen readers to announce the trail correctly. - Separators are decorative — never put meaningful content inside
Breadcrumb.Separator.
- The
-
Routing:
- Use
asChildonBreadcrumb.Linkto compose withnext/link,<a is="astro-link">, or any router-aware component.
- Use
Previous
Badge
Next
Button