Skip to content
    Style: Wireframe

    Blocks · Navigation

    Breadcrumb trail

    A home icon and the levels on one line with chevrons; a long current page truncates instead of wrapping.

    8.7% of the 2,073 sites in The UI Index have a breadcrumb (181 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/ui/r/breadcrumb-trail.json
    Astro
    npx shadcn@latest add https://www.themes.dev/ui/r/astro/breadcrumb-trail.json

    Or copy the files below into your project. They need clsx, tailwind-merge and the components they import, and the themes.dev UI theme. The HTML needs only the compiled CSS.

    breadcrumb-trail.classes.ts
    /** 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
    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>
      );
    }