Breadcrumbs
Free · MITStatic breadcrumb trail with BreadcrumbList microdata that collapses the middle segments into an ellipsis on long paths.
- Category
- Core UI
- Framework
- astro
- Version
- 1.0.0
- Peer deps
- none
Install
velocityx add breadcrumbs
Free and MIT licensed — no account needed. The file lands under src/components/velocity/breadcrumbs/ by default (override with --dest), and it's yours to edit from the moment it's written.
Demo
View the live demo on the showcase site ↗
Source
Pulled live from the registry — this is the exact file velocityx add breadcrumbs writes into your project right now.
components/velocity/breadcrumbs/Breadcrumbs.astro
---
// VELOCITY COMPONENT · theming
//
// Colours here are Tailwind `primary-*` / `accent-*`, which resolve through
// the Velocity preset. Out of the box they are a neutral indigo — NOT the
// Aidxn brand — so this drops into your project without dragging our look in.
//
// ⚡ NOT USING VELOCITY? Run this once, in your project root:
//
// velocityx theme
//
// It writes velocity-preset.cjs + velocity-theme.css. Add the preset to your
// tailwind.config, import the css, then set your whole brand in ONE place:
//
// :root {
// --vx-brand: custom;
// --vx-primary-600: 220 38 38; <- your action colour
// --vx-primary-700: 185 28 28;
// }
//
// ⚠️ Raw RGB channels, space separated — NOT hex. `#DC2626` here silently
// breaks every /opacity variant in the library (bg-primary-600/20 etc).
//
// Prefer not to install anything? Find-and-replace `primary-` with your own
// scale name. Nothing else in this file depends on Velocity.
/**
* Breadcrumbs — static trail nav with BreadcrumbList microdata.
*
* Collapses the middle on small screens to first + ellipsis + last two,
* so a deep path never wraps onto three lines on phone.
*
* Usage:
* <Breadcrumbs items={[{ label: 'Home', href: '/' }, { label: 'Blog', href: '/blog' }, { label: 'Post title' }]} />
*/
export interface Crumb {
label: string;
href?: string;
}
export interface Props {
items: Crumb[];
}
const { items } = Astro.props;
const collapse = items.length > 4;
const head = collapse ? items.slice(0, 1) : items;
const tail = collapse ? items.slice(-2) : [];
---
<nav aria-label="Breadcrumb" class="text-sm">
<ol class="flex flex-wrap items-center gap-1.5" itemscope itemtype="https://schema.org/BreadcrumbList">
{head.map((item, i) => (
<li class="flex items-center gap-1.5" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
{item.href ? (
<a href={item.href} itemprop="item" class="text-secondary-500 dark:text-secondary-400 hover:text-primary-700 dark:hover:text-primary-300 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-600">
<span itemprop="name">{item.label}</span>
</a>
) : (
<span aria-current="page" itemprop="name" class="font-medium text-secondary-900 dark:text-white">{item.label}</span>
)}
<meta itemprop="position" content={String(i + 1)} />
{(i < head.length - 1 || collapse) && (
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-secondary-300 dark:text-secondary-600">
<path d="m9 6 6 6-6 6" />
</svg>
)}
</li>
))}
{collapse && (
<li class="flex items-center gap-1.5 text-secondary-400 dark:text-secondary-500" aria-hidden="true">
<span>…</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-secondary-300 dark:text-secondary-600">
<path d="m9 6 6 6-6 6" />
</svg>
</li>
)}
{tail.map((item, i) => (
<li class="flex items-center gap-1.5" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
{item.href ? (
<a href={item.href} itemprop="item" class="text-secondary-500 dark:text-secondary-400 hover:text-primary-700 dark:hover:text-primary-300 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-600">
<span itemprop="name">{item.label}</span>
</a>
) : (
<span aria-current="page" itemprop="name" class="font-medium text-secondary-900 dark:text-white">{item.label}</span>
)}
<meta itemprop="position" content={String(items.length - tail.length + i + 1)} />
{i < tail.length - 1 && (
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-secondary-300 dark:text-secondary-600">
<path d="m9 6 6 6-6 6" />
</svg>
)}
</li>
))}
</ol>
</nav>
Theming
Uses the shared primary-* / accent-* scale — see Theming if colours look unstyled after install.