Skip to content

Breadcrumbs

import { Breadcrumbs } from '@delightstack/components';
import type { BreadcrumbItem } from '@delightstack/components';
View code
<script>
import { Breadcrumbs } from '@delightstack/components';
import type { BreadcrumbItem } from '@delightstack/components';
const items: BreadcrumbItem[] = [
{ label: 'Products', href: '/products' },
{ label: 'Electronics', href: '/products/electronics' },
{ label: 'Headphones' },
];
</script>
<Breadcrumbs {items} />

With no max_items set, the trail auto-collapses to fit its container — the first item and the last two stay pinned while the middle items fold into the “…” menu, starting from the center. This is done in pure CSS with container queries, so it’s already correct in server-rendered HTML (no layout flash, no JS measuring).

Drag the right edge ↔ to resize. Items fold into the “…” menu to fit.

View code
<!-- Just render the items — collapsing is automatic -->
<Breadcrumbs {items} />

Pass max_items to force the whole middle block into the dropdown at a fixed count.

View code
<script>
import { Breadcrumbs } from '@delightstack/components';
const items = [
{ label: 'Category', href: '/cat' },
{ label: 'Subcategory', href: '/cat/sub' },
{ label: 'Brand', href: '/cat/sub/brand' },
{ label: 'Model', href: '/cat/sub/brand/model' },
{ label: 'Product' },
];
</script>
<Breadcrumbs {items} max_items={3} />

Tightens the spacing between items and separators for compact layouts like toolbars.

View code
<Breadcrumbs {items} dense />

Hide the automatic home breadcrumb.

View code
<Breadcrumbs {items} show_home={false} />

Replace the default chevron separator with a custom snippet.

View code
<Breadcrumbs {items}>
{#snippet separator()}
<span>/</span>
{/snippet}
</Breadcrumbs>
View code
<Breadcrumbs {items} size="0" /> <!-- Small -->
<Breadcrumbs {items} size="1" /> <!-- Default -->
<Breadcrumbs {items} size="2" /> <!-- Medium -->
<Breadcrumbs {items} size="3" /> <!-- Large -->

React to breadcrumb clicks without relying solely on native navigation. The onclick handler fires for crumbs that have no href (crumbs with an href navigate natively).

Promise-aware loading. If your onclick handler returns a promise, the clicked crumb automatically shows a loading spinner until the promise settles, then clears it — the trail re-flows smoothly as the spinner appears and disappears. This is ideal for async work like validating a step or fetching data before the view updates, with no extra loading state to manage yourself.

View code
<script>
import { Breadcrumbs } from '@delightstack/components';
const items = [
{ label: 'Home' },
{ label: 'Products' },
{ label: 'Detail' },
];
let status = $state('');
// Returning a promise drives the per-crumb loading spinner.
function navigate({ item, index }) {
status = `Loading "${item.label}"…`;
return new Promise((resolve) => {
setTimeout(() => {
status = `Clicked "${item.label}" (index ${index})`;
resolve();
}, 1200);
});
}
</script>
<Breadcrumbs {items} show_home={false} onclick={navigate} />

The skeleton mirrors the real trail’s box exactly (size and spacing), so swapping between loading and loaded states never shifts the layout. It scales with size, shows the home icon when show_home is set, and is automatically replaced the moment real items are provided.

View code
<!-- skeleton shows only while items is empty -->
<Breadcrumbs skeleton skeleton_count={3} items={loading ? [] : items} />
PropTypeDefaultDescription
itemsBreadcrumbItem[][]The breadcrumb items to display
max_itemsnumberundefinedMax visible items before collapsing the middle block into the ellipsis dropdown. When omitted, the trail auto-collapses to fit its container width (pure CSS, SSR-safe)
show_homebooleantrueWhether to show a home icon as the first breadcrumb
home_hrefstring'/'The href for the home breadcrumb
size'0' | '1' | '2' | '3''1'Size of the breadcrumbs
densebooleanfalseCondensed spacing between items and separators
skeletonbooleanfalseShow skeleton loading state (only while items is empty)
skeleton_countnumber3Number of skeleton placeholder items
idstringautoElement ID
classstring''Additional CSS classes
childrenSnippetundefinedCustom rendering snippet (replaces default rendering)
separatorSnippetundefinedCustom separator snippet
EventDetailDescription
onclick{ item: BreadcrumbItem, index: number }Fires when a breadcrumb item without an href is clicked. May return a promise — while it’s pending, the clicked crumb shows a loading spinner
interface BreadcrumbItem {
label: string;
href?: string;
}

When max_items is omitted, the component estimates each item’s width from its label length and uses CSS container query units to decide what fits. Because the decision is made entirely in CSS, the correct collapsed state is present in the first server-rendered paint — there’s no measure-then-reflow flash. The first item and the last two are always kept; the middle items fold into the “…” menu from the center outward as space shrinks.

Estimates are intentionally approximate (proportional fonts vary), so leave a little breathing room if precise control matters — or set max_items explicitly.

  • Uses <nav> with aria-label="Breadcrumb"
  • Semantic <ol> list for the breadcrumb trail
  • Last item has aria-current="page"
  • Home icon includes screen-reader-only text (<span class="sr-only">)
  • The ellipsis uses a native <details>/<summary> disclosure (keyboard- and screen-reader-friendly, works without JS)
  • Collapsed (zero-width) copies are marked inert when JS is available, so only the visible copy is focusable and announced
  • Automatically generates Schema.org BreadcrumbList JSON-LD for SEO