component — navigation

Breadcrumbs

Compact navigation trail built on react-aria-components. Set in the nav role: intermediate crumbs are muted links that underline on hover, the current page is full ink, with a chevron or slash separator.

  • navigation
  • breadcrumbs
  • trail
  • links

04 examples

Default

Chevron-separated trail. The last crumb is the current page.

  1. catalog
  2. devices
  3. iPhone 15 Pro Max

Slash separator

Use separator="slash" for narrower trails or beside a page title.

  1. plans/
  2. flex 50/
  3. pricing

With icon

A leading icon (data-slot="icon") inherits the crumb's color.

Two levels

A short trail with a single parent.

  1. dashboard
  2. billing

source

breadcrumbs.tsx

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

"use client"

import { createContext, use } from "react"
import type { BreadcrumbProps, BreadcrumbsProps, LinkProps } from "react-aria-components"
import {
  Breadcrumb,
  Breadcrumbs as BreadcrumbsPrimitive,
  composeRenderProps,
} from "react-aria-components"
import { Link } from "@/components/link"
import { cn } from "@/lib/utils"

/**
 * Breadcrumbs — quebi design system
 *
 * Compact navigation trail, set in the design's nav role (Outfit 14px).
 * Intermediate crumbs are links in the body ink that turn to full ink and
 * underline on hover, like the top navigation; the current crumb is full ink
 * and never underlines. The separator glyph (chevron or slash) is subtle.
 * Built on react-aria-components for the accessibility baseline.
 */

type BreadcrumbsContextProps = { separator?: "chevron" | "slash" | boolean }
const BreadcrumbsProvider = createContext<BreadcrumbsContextProps>({
  separator: "chevron",
})

const Breadcrumbs = <T extends object>({
  className,
  ...props
}: BreadcrumbsProps<T> & BreadcrumbsContextProps) => {
  return (
    <BreadcrumbsProvider value={{ separator: props.separator }}>
      <BreadcrumbsPrimitive
        {...props}
        className={cn("flex items-center gap-2 font-display text-quebi-nav", className)}
      />
    </BreadcrumbsProvider>
  )
}

interface BreadcrumbsItemProps extends BreadcrumbProps, BreadcrumbsContextProps {
  href?: string
}

const BreadcrumbsItem = ({
  href,
  separator = true,
  className,
  ...props
}: BreadcrumbsItemProps & Partial<Omit<LinkProps, "className">>) => {
  const { separator: contextSeparator } = use(BreadcrumbsProvider)
  separator = contextSeparator ?? separator
  const separatorValue = separator === true ? "chevron" : separator

  return (
    <Breadcrumb
      className={composeRenderProps(className, (resolved) =>
        cn("flex items-center gap-2 text-quebi-fg-muted", resolved),
      )}
      data-slot="breadcrumb-item"
      {...props}
    >
      {({ isCurrent }) => (
        <>
          <Link
            className={cn(
              "no-underline",
              "has-data-[slot=icon]:inline-flex has-data-[slot=icon]:items-center has-data-[slot=icon]:gap-x-2",
              "*:data-[slot=icon]:size-4",
              isCurrent
                ? "cursor-default text-quebi-fg hover:no-underline"
                : "text-quebi-fg-muted hover:text-quebi-fg hover:*:data-[slot=icon]:text-quebi-fg",
            )}
            href={href}
            {...props}
          />
          {!isCurrent && separator !== false && <Separator separator={separatorValue} />}
        </>
      )}
    </Breadcrumb>
  )
}

const ChevronRightIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth={1.5}
    strokeLinecap="round"
    strokeLinejoin="round"
    aria-hidden="true"
    data-slot="icon"
  >
    <path d="m9 6 6 6-6 6" />
  </svg>
)

const Separator = ({
  separator = "chevron",
}: {
  separator?: BreadcrumbsItemProps["separator"]
}) => {
  return (
    <span className="*:shrink-0 *:text-quebi-fg-subtle *:data-[slot=icon]:size-3.5">
      {separator === "chevron" && <ChevronRightIcon />}
      {separator === "slash" && <span>/</span>}
    </span>
  )
}

Breadcrumbs.Item = BreadcrumbsItem

export type { BreadcrumbsItemProps, BreadcrumbsProps }
export { Breadcrumbs, BreadcrumbsItem }