Blocks · Navigation
Breadcrumb trail
A home icon and the levels on one line with chevrons; a long current page truncates instead of wrapping.
8.7% of the 2,073 sites in The UI Index have a breadcrumb (181 sites).
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ ui/ r/ breadcrumb-trail.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ ui/ r/ astro/ breadcrumb-trail.json
Or copy the files below into your project. They need clsx, tailwind-merge and the components they import, and the themes.dev UI theme. The HTML needs only the compiled CSS.
/** A breadcrumb trail (ported from the themes' sections/breadcrumb): a home icon for the first level (its label
* stays for screen readers), the levels on one line with chevrons between them, the current page last with
* aria-current. A long current page truncates with an ellipsis instead of wrapping; the levels before it keep
* their size. */
export const breadcrumbTrail = {
root: '@container bg-background px-6 py-8 text-foreground',
inner: 'mx-auto max-w-6xl',
list: 'm-0 -ms-1.5 flex list-none flex-nowrap items-center gap-x-0.5 p-0 text-sm text-muted-foreground @sm:gap-x-1',
item: 'inline-flex shrink-0 items-center gap-0.5 @sm:gap-1',
// Under 24rem of the block's own width the levels between home and the parent fold away, so the current page
// keeps its name; the parent stays as the way up.
itemFold: 'hidden @sm:inline-flex',
itemLast: 'min-w-0 shrink',
link: 'inline-flex min-h-8 items-center rounded-md px-1 transition-colors hover:bg-accent hover:text-foreground hover:underline hover:underline-offset-4 active:bg-pressed @sm:px-1.5',
homeLink: 'inline-flex min-h-8 min-w-8 items-center justify-center rounded-md px-1.5 transition-colors hover:bg-accent hover:text-foreground active:bg-pressed',
homeIcon: 'size-4 shrink-0',
current: 'inline-flex min-h-8 max-w-full min-w-0 items-center px-1 font-medium text-foreground @sm:px-1.5',
currentText: 'truncate',
sep: 'inline-flex shrink-0 text-muted-foreground/70 [&_svg]:size-3.5',
};
/** Drawn here (16px grid, 1.5px stroke): the free icon set has no home glyph. */
export const HOME_PATH = 'M2.5 7.2L8 2.5l5.5 4.7V13a.5.5 0 0 1-.5.5h-3V9.5h-4v4H3a.5.5 0 0 1-.5-.5z';
export type Crumb = {
label: string;
/** Every level but the last; the last is the current page and its href is ignored. */
href?: string;
};
export type BreadcrumbTrailContent = {
/** From the top down; the first is drawn as a home icon, the last is the current page. */
items: Crumb[];
/** The trail's name for screen readers. Default "Breadcrumb". */
label?: string;
};import { Icon } from '@/components/ui/icon';
import { cn } from '@/lib/utils';
import { breadcrumbTrail as c, HOME_PATH, type BreadcrumbTrailContent } from './breadcrumb-trail.classes';
export type BreadcrumbTrailProps = BreadcrumbTrailContent & { className?: string };
/** A breadcrumb trail on one line: home icon, levels with chevrons, a long current page truncates. */
export function BreadcrumbTrail({ items, label = 'Breadcrumb', className }: BreadcrumbTrailProps) {
const last = items.length - 1;
return (
<div className={cn(c.root, className)}>
<div className={c.inner}>
<nav aria-label={label} className="min-w-0">
<ol className={c.list}>
{items.map((item, k) => (
<li key={k} className={cn(c.item, k > 0 && k < last - 1 && c.itemFold, k === last && c.itemLast)}>
{k === last ? (
<span aria-current="page" className={c.current}>
<span className={c.currentText}>{item.label}</span>
</span>
) : k === 0 ? (
<a href={item.href ?? '#'} className={c.homeLink}>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={c.homeIcon}><path d={HOME_PATH} /></svg>
<span className="sr-only">{item.label}</span>
</a>
) : (
<a href={item.href ?? '#'} className={c.link}>{item.label}</a>
)}
{k < last && <span className={c.sep}><Icon name="chevron-right" /></span>}
</li>
))}
</ol>
</nav>
</div>
</div>
);
}---
import Icon from '@/components/ui/icon.astro';
import { cn } from '@/lib/utils';
import { breadcrumbTrail as c, HOME_PATH, type BreadcrumbTrailContent } from './breadcrumb-trail.classes';
type Props = BreadcrumbTrailContent & { class?: string };
const { items, label = 'Breadcrumb', class: className } = Astro.props;
const last = items.length - 1;
---
<div class={cn(c.root, className)}>
<div class={c.inner}>
<nav aria-label={label} class="min-w-0">
<ol class={c.list}>
{
items.map((item, k) => (
<li class={cn(c.item, k > 0 && k < last - 1 && c.itemFold, k === last && c.itemLast)}>
{
k === last ? (
<span aria-current="page" class={c.current}>
<span class={c.currentText}>{item.label}</span>
</span>
) : k === 0 ? (
<a href={item.href ?? '#'} class={c.homeLink}>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class={c.homeIcon}><path d={HOME_PATH} /></svg>
<span class="sr-only">{item.label}</span>
</a>
) : (
<a href={item.href ?? '#'} class={c.link}>{item.label}</a>
)
}
{k < last && <span class={c.sep}><Icon name="chevron-right" /></span>}
</li>
))
}
</ol>
</nav>
</div>
</div>/** A breadcrumb trail (ported from the themes' sections/breadcrumb): a home icon for the first level (its label
* stays for screen readers), the levels on one line with chevrons between them, the current page last with
* aria-current. A long current page truncates with an ellipsis instead of wrapping; the levels before it keep
* their size. */
export const breadcrumbTrail = {
root: '@container bg-background px-6 py-8 text-foreground',
inner: 'mx-auto max-w-6xl',
list: 'm-0 -ms-1.5 flex list-none flex-nowrap items-center gap-x-0.5 p-0 text-sm text-muted-foreground @sm:gap-x-1',
item: 'inline-flex shrink-0 items-center gap-0.5 @sm:gap-1',
// Under 24rem of the block's own width the levels between home and the parent fold away, so the current page
// keeps its name; the parent stays as the way up.
itemFold: 'hidden @sm:inline-flex',
itemLast: 'min-w-0 shrink',
link: 'inline-flex min-h-8 items-center rounded-md px-1 transition-colors hover:bg-accent hover:text-foreground hover:underline hover:underline-offset-4 active:bg-pressed @sm:px-1.5',
homeLink: 'inline-flex min-h-8 min-w-8 items-center justify-center rounded-md px-1.5 transition-colors hover:bg-accent hover:text-foreground active:bg-pressed',
homeIcon: 'size-4 shrink-0',
current: 'inline-flex min-h-8 max-w-full min-w-0 items-center px-1 font-medium text-foreground @sm:px-1.5',
currentText: 'truncate',
sep: 'inline-flex shrink-0 text-muted-foreground/70 [&_svg]:size-3.5',
};
/** Drawn here (16px grid, 1.5px stroke): the free icon set has no home glyph. */
export const HOME_PATH = 'M2.5 7.2L8 2.5l5.5 4.7V13a.5.5 0 0 1-.5.5h-3V9.5h-4v4H3a.5.5 0 0 1-.5-.5z';
export type Crumb = {
label: string;
/** Every level but the last; the last is the current page and its href is ignored. */
href?: string;
};
export type BreadcrumbTrailContent = {
/** From the top down; the first is drawn as a home icon, the last is the current page. */
items: Crumb[];
/** The trail's name for screen readers. Default "Breadcrumb". */
label?: string;
};<div class="grid w-full gap-2">
<div class="@container bg-background px-6 py-8 text-foreground">
<div class="mx-auto max-w-6xl">
<nav aria-label="Breadcrumb" class="min-w-0">
<ol class="m-0 -ms-1.5 flex list-none flex-nowrap items-center gap-x-0.5 p-0 text-sm text-muted-foreground @sm:gap-x-1">
<li class="inline-flex shrink-0 items-center gap-0.5 @sm:gap-1">
<a href="#" class="inline-flex min-h-8 min-w-8 items-center justify-center rounded-md px-1.5 transition-colors hover:bg-accent hover:text-foreground active:bg-pressed">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-4 shrink-0"><path d="M2.5 7.2L8 2.5l5.5 4.7V13a.5.5 0 0 1-.5.5h-3V9.5h-4v4H3a.5.5 0 0 1-.5-.5z"></path></svg>
<span class="sr-only">Home</span>
</a>
<span class="inline-flex shrink-0 text-muted-foreground/70 [&_svg]:size-3.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0"><path d="M6 3.5l4.5 4.5L6 12.5"></path></svg>
</span>
</li>
<li class="shrink-0 items-center gap-0.5 @sm:gap-1 hidden @sm:inline-flex">
<a href="#" class="inline-flex min-h-8 items-center rounded-md px-1 transition-colors hover:bg-accent hover:text-foreground hover:underline hover:underline-offset-4 active:bg-pressed @sm:px-1.5">
Docs
</a>
<span class="inline-flex shrink-0 text-muted-foreground/70 [&_svg]:size-3.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0"><path d="M6 3.5l4.5 4.5L6 12.5"></path></svg>
</span>
</li>
<li class="inline-flex shrink-0 items-center gap-0.5 @sm:gap-1">
<a href="#" class="inline-flex min-h-8 items-center rounded-md px-1 transition-colors hover:bg-accent hover:text-foreground hover:underline hover:underline-offset-4 active:bg-pressed @sm:px-1.5">
Components
</a>
<span class="inline-flex shrink-0 text-muted-foreground/70 [&_svg]:size-3.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0"><path d="M6 3.5l4.5 4.5L6 12.5"></path></svg>
</span>
</li>
<li class="inline-flex items-center gap-0.5 @sm:gap-1 min-w-0 shrink">
<span aria-current="page" class="inline-flex min-h-8 max-w-full min-w-0 items-center px-1 font-medium text-foreground @sm:px-1.5">
<span class="truncate">Breadcrumb</span>
</span>
</li>
</ol>
</nav>
</div>
</div>
<div class="@container bg-background px-6 py-8 text-foreground">
<div class="mx-auto max-w-6xl">
<nav aria-label="Breadcrumb, long page" class="min-w-0">
<ol class="m-0 -ms-1.5 flex list-none flex-nowrap items-center gap-x-0.5 p-0 text-sm text-muted-foreground @sm:gap-x-1">
<li class="inline-flex shrink-0 items-center gap-0.5 @sm:gap-1">
<a href="#" class="inline-flex min-h-8 min-w-8 items-center justify-center rounded-md px-1.5 transition-colors hover:bg-accent hover:text-foreground active:bg-pressed">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-4 shrink-0"><path d="M2.5 7.2L8 2.5l5.5 4.7V13a.5.5 0 0 1-.5.5h-3V9.5h-4v4H3a.5.5 0 0 1-.5-.5z"></path></svg>
<span class="sr-only">Home</span>
</a>
<span class="inline-flex shrink-0 text-muted-foreground/70 [&_svg]:size-3.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0"><path d="M6 3.5l4.5 4.5L6 12.5"></path></svg>
</span>
</li>
<li class="shrink-0 items-center gap-0.5 @sm:gap-1 hidden @sm:inline-flex">
<a href="#" class="inline-flex min-h-8 items-center rounded-md px-1 transition-colors hover:bg-accent hover:text-foreground hover:underline hover:underline-offset-4 active:bg-pressed @sm:px-1.5">
Guides
</a>
<span class="inline-flex shrink-0 text-muted-foreground/70 [&_svg]:size-3.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0"><path d="M6 3.5l4.5 4.5L6 12.5"></path></svg>
</span>
</li>
<li class="inline-flex shrink-0 items-center gap-0.5 @sm:gap-1">
<a href="#" class="inline-flex min-h-8 items-center rounded-md px-1 transition-colors hover:bg-accent hover:text-foreground hover:underline hover:underline-offset-4 active:bg-pressed @sm:px-1.5">
Deploying
</a>
<span class="inline-flex shrink-0 text-muted-foreground/70 [&_svg]:size-3.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0"><path d="M6 3.5l4.5 4.5L6 12.5"></path></svg>
</span>
</li>
<li class="inline-flex items-center gap-0.5 @sm:gap-1 min-w-0 shrink">
<span aria-current="page" class="inline-flex min-h-8 max-w-full min-w-0 items-center px-1 font-medium text-foreground @sm:px-1.5">
<span class="truncate">Migrating a long-running production project to the new deploy pipeline without downtime</span>
</span>
</li>
</ol>
</nav>
</div>
</div>
</div>