# Breadcrumb trail (themes.dev UI block)

A home icon and the levels on one line with chevrons; a long current page truncates instead of wrapping. Black and white, dark mode, shadcn-compatible; React, Astro and plain HTML.

8.7% of the 2,073 sites in The UI Index have a breadcrumb (181 sites).

## 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`
- HTML: https://www.themes.dev/ui/r/html/breadcrumb-trail.html (with https://www.themes.dev/ui/r/html/caseroom.css)

Page: https://www.themes.dev/ui/blocks/breadcrumb-trail/ · Rules for agents: https://www.themes.dev/ui/AGENT.md

## Code

### breadcrumb-trail.astro

```astro
---
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>
```

### breadcrumb-trail.classes.ts

```tsx
/** 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;
};
```

### breadcrumb-trail.tsx

```tsx
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>
  );
}
```
