Breadcrumb

A trail of links that shows where the current page sits in your site and takes the reader back up a level in one click. You compose it from parts, so it holds as many steps as the page needs, and it follows the page's reading direction.

Installation

bash
npx @smicolon/fasla-ui@latest add breadcrumb

Run init once per project before adding components. See Installation.

Preview

Variants

Figma has four styles. BreadcrumbSeparator draws a chevron unless you pass it an icon: a SlashIcon gives the custom separator. The two badge styles are compositions: a Soft or Outline Badge inside each step.

Default
Custom separator
With badge
Outline badge

Icons

Add an icon before a label with the icon prop of BreadcrumbLink or BreadcrumbPage. It is sized to 14px with a 4px gap, so pass the icon without a size of its own. In the badge styles, pass the icon to the Badge instead, through its own icon prop, which sizes and spaces it. Icons are decorative, so screen readers skip them.

Collapsed trail

When the trail is long, keep the first step and the last two, and put BreadcrumbEllipsis in place of the steps between them. The order is the same in both directions. To let the reader reach the hidden steps, make the ellipsis the trigger of a DropdownMenu that lists them as links, as these examples do: press the ellipsis to open it. On its own, the ellipsis only marks the gap.

Accessibility

  • Breadcrumb is a navigation landmark. Name it in the page's language with aria-label, as the examples here do; without one it is named Breadcrumb. The steps are an ordered list, so screen readers announce how many there are.
  • BreadcrumbPage carries aria-current="page". It is plain text, not a link to the page the reader is already on.
  • Separators and icons are hidden from screen readers. The ellipsis is read as “More”, or “المزيد” when the nearest lang is Arabic.
  • As a menu trigger, the ellipsis is a button. Name it with aria-label, as the examples here do, so screen readers say what the menu holds. Enter, Space or the down arrow opens the menu, the arrow keys move between the steps, Enter follows a link, and Escape closes the menu and returns focus to the ellipsis.
  • Links, and the ellipsis when it opens a menu, show a focus ring when you reach them with the keyboard.
  • The trail follows the page's dir: in Arabic it runs from right to left and the chevron turns to match. The taller Arabic line comes from the type scale, with nothing to set.

Props

Breadcrumb

PropTypeDefaultDescription
aria-labelstring"Breadcrumb"The landmark's name for screen readers, in the page's language.
childrenReactNode—The BreadcrumbList.
classNamestring—Extra classes for the nav element.

BreadcrumbList

PropTypeDefaultDescription
childrenReactNode—The steps and separators, in reading order.
classNamestring—Extra classes for the ol element.

BreadcrumbItem

PropTypeDefaultDescription
childrenReactNode—One step: a link, the current page or the ellipsis.
classNamestring—Extra classes for the li element.

BreadcrumbLink

PropTypeDefaultDescription
hrefstring—Where the step goes. The link takes every other a attribute too.
asChildbooleanfalseMakes its child, such as a Next.js Link, the link.
iconReactNode—An icon before the label, sized to 14px.
childrenReactNode—The step's label.
classNamestring—Extra classes for the link.

BreadcrumbPage

PropTypeDefaultDescription
iconReactNode—An icon before the label, sized to 14px.
childrenReactNode—The current page's name.
classNamestring—Extra classes for the current page.

BreadcrumbSeparator

PropTypeDefaultDescription
childrenReactNode—An icon in place of the chevron, such as SlashIcon.
classNamestring—Extra classes for the separator.

BreadcrumbEllipsis

PropTypeDefaultDescription
classNamestring—Extra classes for the ellipsis. Its screen-reader text is built in.

Usage

Basic trail

tsx
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"

export function DocumentBreadcrumb() {
  return (
    <Breadcrumb aria-label="Breadcrumb">
      <BreadcrumbList>
        <BreadcrumbItem>
          <BreadcrumbLink href="/">Home</BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbLink href="/projects">
            Projects
          </BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbLink href="/docs">
            Documents
          </BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbPage>Add Document</BreadcrumbPage>
        </BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  )
}

Collapsed trail

tsx
import { HouseIcon } from "lucide-react"
import {
  Breadcrumb,
  BreadcrumbEllipsis,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

const TRIGGER = [
  "-m-[5px] inline-flex size-6 cursor-pointer items-center",
  "justify-center rounded-[var(--radius-sm)] transition-colors",
  "hover:bg-accent data-[popup-open]:bg-accent",
  "focus-visible:outline focus-visible:outline-2",
  "focus-visible:outline-ring motion-reduce:transition-none",
].join(" ")

export function CollapsedBreadcrumb() {
  return (
    <Breadcrumb aria-label="Breadcrumb">
      <BreadcrumbList>
        <BreadcrumbItem>
          <BreadcrumbLink href="/" icon={<HouseIcon />}>
            Home
          </BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <DropdownMenu>
            <DropdownMenuTrigger
              aria-label="Hidden steps"
              className={TRIGGER}
            >
              <BreadcrumbEllipsis />
            </DropdownMenuTrigger>
            <DropdownMenuContent>
              <DropdownMenuItem render={<a href="/projects" />}>
                Projects
              </DropdownMenuItem>
              <DropdownMenuItem render={<a href="/projects/website" />}>
                Website
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbLink href="/docs">Documents</BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbPage>Add Document</BreadcrumbPage>
        </BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  )
}

Custom separator

tsx
import { SlashIcon } from "lucide-react"
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"

export function SlashBreadcrumb() {
  return (
    <Breadcrumb aria-label="Breadcrumb">
      <BreadcrumbList>
        <BreadcrumbItem>
          <BreadcrumbLink href="/">Home</BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator>
          <SlashIcon />
        </BreadcrumbSeparator>
        <BreadcrumbItem>
          <BreadcrumbPage>Add Document</BreadcrumbPage>
        </BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  )
}

With a Next.js link

tsx
import Link from "next/link"
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"

export function ProjectBreadcrumb() {
  return (
    <Breadcrumb aria-label="Breadcrumb">
      <BreadcrumbList>
        <BreadcrumbItem>
          <BreadcrumbLink asChild>
            <Link href="/projects">Projects</Link>
          </BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbPage>Documents</BreadcrumbPage>
        </BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  )
}

With badges

tsx
import { SlashIcon } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"

const DIMMED = [
  "rounded-full opacity-60",
  "hover:opacity-100 focus-visible:opacity-100",
].join(" ")

export function DepartmentBreadcrumb() {
  return (
    <Breadcrumb aria-label="Breadcrumb">
      <BreadcrumbList>
        <BreadcrumbItem>
          <BreadcrumbLink href="/marketing" className={DIMMED}>
            <Badge variant="soft" size="md">
              Marketing
            </Badge>
          </BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator>
          <SlashIcon />
        </BreadcrumbSeparator>
        <BreadcrumbItem>
          <BreadcrumbPage>
            <Badge variant="soft" size="md">
              Digital Marketing
            </Badge>
          </BreadcrumbPage>
        </BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  )
}