Components · Navigation
Breadcrumb
Where the page sits: a nav with an ordered list and the current page marked.
38% of the 2,062 sites in The UI Index have a navigation (781 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/ caseroom/ r/ breadcrumb.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ astro/ breadcrumb.json
Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS.
import { breadcrumb, SEPARATOR_PATH, type Crumb } from './breadcrumb.variants';
export type { Crumb };
export interface BreadcrumbProps {
/** From the top down; the last one is the current page (its href is ignored). */
items: Crumb[];
label?: string;
className?: string;
}
/** A breadcrumb trail: nav > ol, the current page with aria-current, separators in CSS. */
export function Breadcrumb({ items, label = 'Breadcrumb', className }: BreadcrumbProps) {
return (
<nav aria-label={label} className={className}>
<ol className={breadcrumb.list}>
{items.map((c, k) => (
<li key={k} className={breadcrumb.item}>
{k === items.length - 1 ? <span aria-current="page" className={breadcrumb.current}>{c.label}</span> : <a href={c.href} className={breadcrumb.link}>{c.label}</a>}
{k < items.length - 1 && (
<span className={breadcrumb.separator}>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={SEPARATOR_PATH} /></svg>
</span>
)}
</li>
))}
</ol>
</nav>
);
}/** Where the page sits: a nav with an ordered list, the current page last with aria-current. The separators are
* hidden from screen readers (aria-hidden). */
export const breadcrumb = {
list: 'm-0 flex list-none flex-wrap items-center gap-x-1.5 gap-y-1 p-0 text-sm text-muted-foreground',
// The separator belongs to the crumb before it, so a wrapped trail never starts a line with one.
item: 'inline-flex items-center gap-1.5 whitespace-nowrap',
separator: 'inline-flex text-muted-foreground/70 [&_svg]:size-3.5',
link: '-mx-1 inline-flex min-h-6 items-center rounded-sm px-1 transition-colors hover:text-foreground hover:underline hover:underline-offset-4 active:text-foreground/70',
current: 'font-medium text-foreground',
};
export type Crumb = { label: string; href?: string };
export const SEPARATOR_PATH = 'M6 3.5l4.5 4.5L6 12.5';---
import { cn } from '@/lib/utils';
import { breadcrumb, SEPARATOR_PATH, type Crumb } from './breadcrumb.variants';
interface Props {
/** From the top down; the last one is the current page (its href is ignored). */
items: Crumb[];
label?: string;
class?: string;
}
const { items, label = 'Breadcrumb', class: className } = Astro.props;
---
<nav aria-label={label} class={className}>
<ol class={breadcrumb.list}>
{
items.map((c, k) => (
<li class={breadcrumb.item}>
{k === items.length - 1 ? <span aria-current="page" class={breadcrumb.current}>{c.label}</span> : <a href={c.href} class={breadcrumb.link}>{c.label}</a>}
{k < items.length - 1 && <span class={breadcrumb.separator}><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d={SEPARATOR_PATH} /></svg></span>}
</li>
))
}
</ol>
</nav>/** Where the page sits: a nav with an ordered list, the current page last with aria-current. The separators are
* hidden from screen readers (aria-hidden). */
export const breadcrumb = {
list: 'm-0 flex list-none flex-wrap items-center gap-x-1.5 gap-y-1 p-0 text-sm text-muted-foreground',
// The separator belongs to the crumb before it, so a wrapped trail never starts a line with one.
item: 'inline-flex items-center gap-1.5 whitespace-nowrap',
separator: 'inline-flex text-muted-foreground/70 [&_svg]:size-3.5',
link: '-mx-1 inline-flex min-h-6 items-center rounded-sm px-1 transition-colors hover:text-foreground hover:underline hover:underline-offset-4 active:text-foreground/70',
current: 'font-medium text-foreground',
};
export type Crumb = { label: string; href?: string };
export const SEPARATOR_PATH = 'M6 3.5l4.5 4.5L6 12.5';<nav aria-label="Breadcrumb">
<ol class="m-0 flex list-none flex-wrap items-center gap-x-1.5 gap-y-1 p-0 text-sm text-muted-foreground">
<li class="inline-flex items-center gap-1.5 whitespace-nowrap">
<a href="#" class="-mx-1 inline-flex min-h-6 items-center rounded-sm px-1 transition-colors hover:text-foreground hover:underline hover:underline-offset-4 active:text-foreground/70">
Projects
</a>
<span class="inline-flex 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"><path d="M6 3.5l4.5 4.5L6 12.5"></path></svg>
</span>
</li>
<li class="inline-flex items-center gap-1.5 whitespace-nowrap">
<a href="#" class="-mx-1 inline-flex min-h-6 items-center rounded-sm px-1 transition-colors hover:text-foreground hover:underline hover:underline-offset-4 active:text-foreground/70">
Northwind
</a>
<span class="inline-flex 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"><path d="M6 3.5l4.5 4.5L6 12.5"></path></svg>
</span>
</li>
<li class="inline-flex items-center gap-1.5 whitespace-nowrap">
<a href="#" class="-mx-1 inline-flex min-h-6 items-center rounded-sm px-1 transition-colors hover:text-foreground hover:underline hover:underline-offset-4 active:text-foreground/70">
Deploys
</a>
<span class="inline-flex 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"><path d="M6 3.5l4.5 4.5L6 12.5"></path></svg>
</span>
</li>
<li class="inline-flex items-center gap-1.5 whitespace-nowrap"><span aria-current="page" class="font-medium text-foreground">Production</span></li>
</ol>
</nav>