Breadcrumb
Array-driven navigation trail with optional icons, current page indication, and smart truncation for long paths.
<!-- Breadcrumb -->
<!-- An Alpine.js and Tailwind CSS component by https://pinemix.com -->
<div
class="flex flex-col items-center justify-center gap-5 rounded-lg border-2 border-dashed border-zinc-200/75 px-4 py-44 dark:border-zinc-700"
>
<div
x-data="{
// Customize Breadcrumb
items: [
{ label: 'Components', href: 'javascript:void(0)', icon: 'home' },
{ label: 'Navigation', href: 'javascript:void(0)' },
{ label: 'Breadcrumb' }
],
maxVisible: 3,
// Helper variables
expanded: false,
// Build the visible trail with an ellipsis placeholder when needed.
// 'maxVisible' caps how many crumb items remain visible after truncation
// (the ellipsis itself does not count). The first crumb and the last
// 'maxVisible - 1' crumbs are kept; everything in between collapses.
get trail() {
const items = this.items;
if (this.expanded || items.length <= this.maxVisible) {
return items.map((item, i) => ({ kind: 'item', item, index: i }));
}
const visible = Math.max(2, this.maxVisible);
const tailCount = visible - 1;
const trail = [{ kind: 'item', item: items[0], index: 0 }];
trail.push({ kind: 'ellipsis' });
const tailStart = items.length - tailCount;
for (let i = tailStart; i < items.length; i++) {
trail.push({ kind: 'item', item: items[i], index: i });
}
return trail;
},
// Helpers
isCurrent(index) {
return index === this.items.length - 1;
},
hiddenCount() {
return Math.max(0, this.items.length - Math.max(2, this.maxVisible));
}
}"
>
<!-- Breadcrumb Nav -->
<nav aria-label="Breadcrumb">
<ol class="flex flex-wrap items-center gap-1.5 text-sm">
<template x-for="(node, i) in trail" :key="i">
<li class="flex items-center gap-1.5">
<!-- Separator -->
<template x-if="i > 0">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-chevron-right inline-block size-4 text-zinc-400 rtl:rotate-180 dark:text-zinc-500"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M6.22 4.22a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06L8.94 8 6.22 5.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>
</template>
<!-- END Separator -->
<!-- Ellipsis Toggle -->
<template x-if="node.kind === 'ellipsis'">
<button
x-on:click="expanded = true"
type="button"
x-bind:aria-label="'Show ' + hiddenCount() + ' hidden breadcrumb items'"
class="inline-flex items-center justify-center rounded-md px-1.5 py-1 text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900 focus:bg-zinc-100 focus:outline-hidden dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100 dark:focus:bg-zinc-800"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-ellipsis-horizontal inline-block size-4"
aria-hidden="true"
>
<path
d="M3 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Zm5 0a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Zm6.5-1.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
/>
</svg>
</button>
</template>
<!-- END Ellipsis Toggle -->
<!-- Crumb Link -->
<template x-if="node.kind === 'item' && !isCurrent(node.index)">
<a
x-bind:href="node.item.href"
class="inline-flex max-w-40 items-center gap-1.5 truncate rounded-md px-1.5 py-0.5 font-medium text-zinc-600 hover:bg-zinc-100 hover:text-zinc-900 focus:bg-zinc-100 focus:outline-hidden dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100 dark:focus:bg-zinc-800"
>
<!-- Optional Home Icon -->
<template x-if="node.item.icon === 'home'">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-home inline-block size-4 shrink-0 opacity-50"
aria-hidden="true"
>
<path
d="M8.543 2.232a.75.75 0 0 0-1.085 0l-5.25 5.5A.75.75 0 0 0 2.75 9H4v4a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1v-1a1 1 0 1 1 2 0v1a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V9h1.25a.75.75 0 0 0 .543-1.268l-5.25-5.5Z"
/>
</svg>
</template>
<!-- END Optional Home Icon -->
<span class="truncate" x-text="node.item.label"></span>
</a>
</template>
<!-- END Crumb Link -->
<!-- Current Page -->
<template x-if="node.kind === 'item' && isCurrent(node.index)">
<span
aria-current="page"
class="inline-flex max-w-48 items-center gap-1.5 truncate px-1.5 py-0.5 font-semibold text-zinc-900 dark:text-zinc-100"
>
<template x-if="node.item.icon === 'home'">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-home inline-block size-4 shrink-0"
aria-hidden="true"
>
<path
d="M8.543 2.232a.75.75 0 0 0-1.085 0l-5.25 5.5A.75.75 0 0 0 2.75 9H4v4a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1v-1a1 1 0 1 1 2 0v1a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V9h1.25a.75.75 0 0 0 .543-1.268l-5.25-5.5Z"
/>
</svg>
</template>
<span class="truncate" x-text="node.item.label"></span>
</span>
</template>
<!-- END Current Page -->
</li>
</template>
</ol>
</nav>
<!-- END Breadcrumb Nav -->
</div>
</div>
<!-- END Breadcrumb -->
With Truncation
<!-- Breadcrumb: With Truncation -->
<!-- An Alpine.js and Tailwind CSS component by https://pinemix.com -->
<div
class="flex flex-col items-center justify-center gap-5 rounded-lg border-2 border-dashed border-zinc-200/75 px-4 py-44 dark:border-zinc-700"
>
<div
x-data="{
// Customize Breadcrumb
items: [
{ label: 'Home', href: 'javascript:void(0)', icon: 'home' },
{ label: 'Docs', href: 'javascript:void(0)' },
{ label: 'Components', href: 'javascript:void(0)' },
{ label: 'Navigation', href: 'javascript:void(0)' },
{ label: 'Breadcrumb' }
],
maxVisible: 2,
// Helper variables
expanded: false,
// Build the visible trail with an ellipsis placeholder when needed.
// 'maxVisible' caps how many crumb items remain visible after truncation
// (the ellipsis itself does not count). The first crumb and the last
// 'maxVisible - 1' crumbs are kept; everything in between collapses.
get trail() {
const items = this.items;
if (this.expanded || items.length <= this.maxVisible) {
return items.map((item, i) => ({ kind: 'item', item, index: i }));
}
const visible = Math.max(2, this.maxVisible);
const tailCount = visible - 1;
const trail = [{ kind: 'item', item: items[0], index: 0 }];
trail.push({ kind: 'ellipsis' });
const tailStart = items.length - tailCount;
for (let i = tailStart; i < items.length; i++) {
trail.push({ kind: 'item', item: items[i], index: i });
}
return trail;
},
isCurrent(index) {
return index === this.items.length - 1;
},
hiddenCount() {
return Math.max(0, this.items.length - Math.max(2, this.maxVisible));
}
}"
>
<!-- Breadcrumb Nav -->
<nav aria-label="Breadcrumb">
<ol class="flex flex-wrap items-center gap-1.5 text-sm">
<template x-for="(node, i) in trail" :key="i">
<li class="flex items-center gap-1.5">
<!-- Separator -->
<template x-if="i > 0">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-chevron-right inline-block size-4 text-zinc-400 rtl:rotate-180 dark:text-zinc-500"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M6.22 4.22a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06L8.94 8 6.22 5.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>
</template>
<!-- END Separator -->
<!-- Ellipsis Toggle -->
<template x-if="node.kind === 'ellipsis'">
<button
x-on:click="expanded = true"
type="button"
x-bind:aria-label="'Show ' + hiddenCount() + ' hidden breadcrumb items'"
class="inline-flex items-center justify-center rounded-md px-1.5 py-1 text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900 focus:bg-zinc-100 focus:outline-hidden dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100 dark:focus:bg-zinc-800"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-ellipsis-horizontal inline-block size-4"
aria-hidden="true"
>
<path
d="M3 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Zm5 0a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Zm6.5-1.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
/>
</svg>
</button>
</template>
<!-- END Ellipsis Toggle -->
<!-- Crumb Link -->
<template x-if="node.kind === 'item' && !isCurrent(node.index)">
<a
x-bind:href="node.item.href"
class="inline-flex max-w-40 items-center gap-1.5 truncate rounded-md px-1.5 py-0.5 font-medium text-zinc-600 hover:bg-zinc-100 hover:text-zinc-900 focus:bg-zinc-100 focus:outline-hidden dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100 dark:focus:bg-zinc-800"
>
<template x-if="node.item.icon === 'home'">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="hi-micro hi-home inline-block size-4 shrink-0 opacity-50"
aria-hidden="true"
>
<path
d="M8.543 2.232a.75.75 0 0 0-1.085 0l-5.25 5.5A.75.75 0 0 0 2.75 9H4v4a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1v-1a1 1 0 1 1 2 0v1a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V9h1.25a.75.75 0 0 0 .543-1.268l-5.25-5.5Z"
/>
</svg>
</template>
<span class="truncate" x-text="node.item.label"></span>
</a>
</template>
<!-- END Crumb Link -->
<!-- Current Page -->
<template x-if="node.kind === 'item' && isCurrent(node.index)">
<span
aria-current="page"
class="inline-flex max-w-48 items-center gap-1.5 truncate px-1.5 py-0.5 font-semibold text-zinc-900 dark:text-zinc-100"
>
<span class="truncate" x-text="node.item.label"></span>
</span>
</template>
<!-- END Current Page -->
</li>
</template>
</ol>
</nav>
<!-- END Breadcrumb Nav -->
</div>
</div>
<!-- END Breadcrumb: With Truncation -->
Props
The available data properties for this component.
| Property | Default | Description |
|---|---|---|
| items | [] | Array of breadcrumb items with shape { label, href?, icon? }. The last item is rendered as the current page and is not linked. |
| maxVisible | 3 | When the number of items exceeds this value, the middle ones collapse into an expandable ellipsis button. |
About this component
Designed with
Tailkit
2,000+ Tailwind CSS code snippets for HTML, React, Vue.js and Alpine.js. AI-powered development with MCP Server.
Unlock 15+ free templates
Join our pixelcave newsletter to get them now & we'll also keep you updated about any new Pinemix components!