# Breadcrumb

> Show the current page's position and provide links to its parent levels.

Source: https://www.honestui.com/docs/components/breadcrumb

```tsx
import Link from "next/link";
import { House as HomeIcon } from "honestui/icons";

import {
  Breadcrumb,
  BreadcrumbDropdownItem,
  BreadcrumbDropdownTrigger,
  BreadcrumbEllipsis,
  BreadcrumbIcon,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/honest-ui/ui/breadcrumb";
import {
  Menu,
  MenuItem,
  MenuPopup,
  MenuTrigger,
} from "@/components/honest-ui/ui/menu";

export function BreadcrumbDemo() {
  return (
    <Breadcrumb>
      <BreadcrumbList>
        <BreadcrumbItem>
          <BreadcrumbLink render={<Link href="/" />}>
            <BreadcrumbIcon>
              <HomeIcon aria-hidden="true" />
            </BreadcrumbIcon>
            Home
          </BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <Menu>
            <MenuTrigger
              render={
                <BreadcrumbDropdownTrigger aria-label="Show hidden breadcrumb levels" />
              }
            >
              <BreadcrumbEllipsis />
            </MenuTrigger>
            <MenuPopup align="start">
              <MenuItem
                render={
                  <BreadcrumbDropdownItem render={<Link href="/docs" />} />
                }
              >
                Docs
              </MenuItem>
              <MenuItem
                render={
                  <BreadcrumbDropdownItem
                    render={<Link href="/docs/get-started" />}
                  />
                }
              >
                Get started
              </MenuItem>
            </MenuPopup>
          </Menu>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbLink render={<Link href="/docs/components/button" />}>
            Components
          </BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbPage>Breadcrumb</BreadcrumbPage>
        </BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  );
}

```

## Overview [#overview]

Use breadcrumbs to show where the current page sits in a hierarchy and to let people jump up one or more levels: documentation, settings, nested admin sections, folders, product categories. They answer "where am I?" — they do not replace main navigation, which should already expose the top levels of the site.

Keep them honest: every level shown should reflect the real structure people navigated through, not a flattened marketing path. See [Don't do this](#dont-do-this) for the two ways this component most often breaks.

## Anatomy [#anatomy]

A breadcrumb has a navigation landmark, an ordered list, items, links for ancestor levels, separators, and the current page. Earlier items are links rendered as anchors; the current item is plain text marked with `aria-current="page"` so assistive technology can tell location from destinations. `BreadcrumbIcon` aligns an icon with a label, and the dropdown parts (`BreadcrumbDropdownTrigger`, `BreadcrumbDropdownItem`) let a Menu hold levels that do not fit.

`BreadcrumbSeparator` defaults to a chevron and accepts any child, such as a bullet or slash. It renders as a list item hidden from assistive technology, so custom separators stay purely visual.

## Usage guidance [#usage-guidance]

Start at the highest useful parent, not automatically the site root; in deep apps the workspace or section name beats "Home". On narrow screens collapse middle levels into the ellipsis menu instead of truncating the current page — the current page is the only level that cannot be clicked elsewhere, so it must never be sacrificed first.

Labels are short nouns because they sit between separators on one line: `Docs / Components / Breadcrumb`, not full page titles. The root offers `size="small"` for dense surfaces like headers and cards.

## Accessibility [#accessibility]

`Breadcrumb` renders a `<nav>` landmark labeled "breadcrumb", so screen-reader users can jump straight to it and hear the trail announced as a list. Key behaviors:

* `BreadcrumbPage` carries `aria-current="page"`, which is what makes the current position distinguishable from the clickable ancestors.
* Separators are `aria-hidden` and `role="presentation"`; they are decoration, not content.
* `BreadcrumbEllipsis` includes a visually hidden "More" label so the collapsed levels are announced rather than read as nothing.
* Give an icon-only dropdown trigger an `aria-label` (for example, "Show hidden breadcrumb levels") — it has no text of its own.
* Every hidden level inside the dropdown menu stays keyboard reachable through normal menu navigation.

Links show a visible focus ring via `focus-visible`. Colors come from theme tokens, so link, hover, and current-page colors adapt to dark mode automatically.

Two things need manual care. Labels use `white-space: nowrap`, so very long localized names overflow instead of wrapping — shorten labels or collapse levels before they collide. The default chevron separator points right and does not flip automatically under `dir="rtl"`; either flip it with a utility like `rtl:rotate-180` on the separator's child or choose a direction-neutral separator.

## Installation [#installation]


  

  
    <CliBlock commands="[&#x22;breadcrumb&#x22;]" />
  

  
    
      
        Copy and paste the following code into your project.
      

      ### components/ui/breadcrumb.tsx

```tsx
import * as React from "react"
import { mergeProps } from "@base-ui-components/react/merge-props"
import { useRender } from "@base-ui-components/react/use-render"
import { cva, type VariantProps } from "class-variance-authority"
import { ChevronRight, Ellipsis as MoreHorizontal } from "honestui/icons"

import { cn } from "@/lib/utils"

const breadcrumbVariants = cva("[font-weight:var(--hui-font-weight-medium)]", {
  variants: {
    size: {
      small:
        "[font-size:var(--hui-font-size-small)] [letter-spacing:var(--hui-letter-spacing-small)] [line-height:var(--hui-line-height-small)]",
      medium:
        "[font-size:var(--hui-font-size-regular)] [letter-spacing:var(--hui-letter-spacing-regular)] [line-height:var(--hui-line-height-regular)]",
    },
  },
  defaultVariants: {
    size: "medium",
  },
})

interface BreadcrumbProps extends React.ComponentProps<"nav"> {
  size?: VariantProps<typeof breadcrumbVariants>["size"]
}

function Breadcrumb({ className, size, ...props }: BreadcrumbProps) {
  return (
    <nav
      aria-label="breadcrumb"
      data-slot="breadcrumb"
      className={cn(breadcrumbVariants({ size }), className)}
      {...props}
    />
  )
}

function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
  return (
    <ol
      data-slot="breadcrumb-list"
      className={cn(
        "m-0 flex list-none items-center gap-[var(--hui-space-2)] p-0",
        className
      )}
      {...props}
    />
  )
}

function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
  return (
    <li
      data-slot="breadcrumb-item"
      className={cn("flex items-center whitespace-nowrap", className)}
      {...props}
    />
  )
}

function BreadcrumbLink({
  className,
  render,
  ...props
}: useRender.ComponentProps<"a">) {
  const defaultProps = {
    "data-slot": "breadcrumb-link",
    className: cn(
      "flex cursor-pointer items-center gap-[var(--hui-space-2)] whitespace-nowrap text-[var(--hui-color-foreground-base-tertiary)] no-underline outline-none motion-safe:[transition:color_var(--hui-duration-fast)_var(--hui-ease-out)] hover:text-[var(--hui-color-foreground-base-secondary)] focus-visible:rounded-[var(--hui-radius-1)] focus-visible:[outline:var(--hui-focus-ring)] aria-disabled:pointer-events-none aria-disabled:opacity-50",
      className
    ),
  }

  return useRender({
    defaultTagName: "a",
    render,
    props: mergeProps<"a">(defaultProps, props),
  })
}

function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="breadcrumb-page"
      role="link"
      aria-disabled="true"
      aria-current="page"
      className={cn(
        "flex cursor-default items-center whitespace-nowrap text-[var(--hui-color-foreground-base-primary)] [font-weight:var(--hui-font-weight-medium)] hover:text-[var(--hui-color-foreground-base-primary)]",
        className
      )}
      {...props}
    />
  )
}

function BreadcrumbSeparator({
  children,
  className,
  ...props
}: React.ComponentProps<"li">) {
  return (
    <li
      data-slot="breadcrumb-separator"
      role="presentation"
      aria-hidden="true"
      className={cn(
        "flex items-center text-[var(--hui-color-foreground-base-tertiary)] [&>svg]:size-4",
        className
      )}
      {...props}
    >
      {children ?? <ChevronRight />}
    </li>
  )
}

function BreadcrumbEllipsis({
  className,
  ...props
}: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="breadcrumb-ellipsis"
      role="presentation"
      aria-hidden="true"
      className={cn(
        "flex items-center text-[var(--hui-color-foreground-base-tertiary)]",
        className
      )}
      {...props}
    >
      <MoreHorizontal className="size-4" />
      <span className="sr-only">More</span>
    </span>
  )
}

function BreadcrumbIcon({ className, ...props }: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="breadcrumb-icon"
      className={cn("flex items-center", className)}
      {...props}
    />
  )
}

function BreadcrumbDropdownTrigger({
  className,
  render,
  ...props
}: useRender.ComponentProps<"button">) {
  const defaultProps = {
    "data-slot": "breadcrumb-dropdown-trigger",
    type: "button" as const,
    className: cn(
      "flex cursor-pointer items-center border-0 bg-transparent p-0 text-[var(--hui-color-foreground-base-secondary)] [font-size:inherit] [font-weight:inherit] outline-none focus-visible:rounded-[var(--hui-radius-1)] focus-visible:[outline:var(--hui-focus-ring)] [&_svg]:h-[var(--hui-space-3)]",
      className
    ),
  }

  return useRender({
    defaultTagName: "button",
    render,
    props: mergeProps<"button">(defaultProps, props),
  })
}

function BreadcrumbDropdownItem({
  className,
  render,
  ...props
}: useRender.ComponentProps<"a">) {
  const defaultProps = {
    "data-slot": "breadcrumb-dropdown-item",
    className: cn(
      "block w-full cursor-pointer border-0 bg-transparent p-[var(--hui-space-3)] text-left text-[var(--hui-color-foreground-base-primary)] no-underline [font-size:var(--hui-font-size-small)] [font-weight:var(--hui-font-weight-regular)] [letter-spacing:var(--hui-letter-spacing-small)] [line-height:var(--hui-line-height-small)] hover:rounded-[var(--hui-radius-2)] hover:bg-[var(--hui-color-background-base-primary-hover)]",
      className
    ),
  }

  return useRender({
    defaultTagName: "a",
    render,
    props: mergeProps<"a">(defaultProps, props),
  })
}

export {
  Breadcrumb,
  BreadcrumbList,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbPage,
  BreadcrumbSeparator,
  BreadcrumbEllipsis,
  BreadcrumbIcon,
  BreadcrumbDropdownTrigger,
  BreadcrumbDropdownItem,
  breadcrumbVariants,
}

```

      
        Update the import paths to match your project setup.
      
    
  


## Usage [#usage]

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

```tsx
<Breadcrumb>
  <BreadcrumbList>
    <BreadcrumbItem>
      <BreadcrumbLink href="/">Home</BreadcrumbLink>
    </BreadcrumbItem>
    <BreadcrumbSeparator />
    <BreadcrumbItem>
      <BreadcrumbEllipsis />
    </BreadcrumbItem>
    <BreadcrumbItem>
      <BreadcrumbLink href="/components">Components</BreadcrumbLink>
    </BreadcrumbItem>
    <BreadcrumbSeparator />
    <BreadcrumbItem>
      <BreadcrumbPage>Breadcrumb</BreadcrumbPage>
    </BreadcrumbItem>
  </BreadcrumbList>
</Breadcrumb>
```

Pass `render={<Link href="..." />}` to `BreadcrumbLink` to compose with your router while keeping breadcrumb styling. `BreadcrumbPage` is not a link; render it once, at the end, for the page the person is on.

## Don't do this [#dont-do-this]

### Navigating with onClick buttons [#navigating-with-onclick-buttons]

```tsx
// Bad
<BreadcrumbItem>
  <Button onClick={() => router.push("/docs")}>Home</Button>
</BreadcrumbItem>
```

```tsx
// Good
<BreadcrumbItem>
  <BreadcrumbLink href="/docs">Home</BreadcrumbLink>
</BreadcrumbItem>
```

A button cannot be opened in a new tab with middle-click or <kbd>Cmd</kbd> click, shows no URL on hover, and is announced as a button, so screen-reader users navigating the landmarks expect an action instead of a destination. Breadcrumbs are navigation; render real anchors, composing with your router through the `render` prop when needed.

### Marking the wrong item as current [#marking-the-wrong-item-as-current]

```tsx
// Bad
<BreadcrumbItem>
  <BreadcrumbLink href="/docs/settings">Settings</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />

<BreadcrumbItem>
  <BreadcrumbLink href="/docs/settings/members" aria-current="page">
    Members
  </BreadcrumbLink>
</BreadcrumbItem>
```

```tsx
// Good
<BreadcrumbItem>
  <BreadcrumbPage>Members</BreadcrumbPage>
</BreadcrumbItem>
```

The current-page marker tells assistive technology where the person *is*. Leaving it off — or putting `aria-current` on another link, or styling the current page as a clickable link — makes the whole trail ambiguous about which level is active. Exactly one item exists, it uses `BreadcrumbPage`, and it sits last.

## Examples [#examples]

### With custom separator [#with-custom-separator]

Any small element works as a separator as long as it still reads as "between": slashes, bullets, dashes. Structure and accessibility are unchanged.

```tsx
import Link from "next/link"

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

export function BreadcrumbDemo() {
  return (
    <Breadcrumb>
      <BreadcrumbList>
        <BreadcrumbItem>
          <BreadcrumbLink render={<Link href="/" />}>
            Home
          </BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator> / </BreadcrumbSeparator>
        <BreadcrumbItem>
          <BreadcrumbLink render={<Link href="/docs/components" />}>
            Components
          </BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator> / </BreadcrumbSeparator>
        <BreadcrumbItem>
          <BreadcrumbPage>Breadcrumb</BreadcrumbPage>
        </BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  )
}

```

### With collapsed levels [#with-collapsed-levels]

When the hierarchy is deep, hide middle levels behind the ellipsis and keep both ends of the trail visible.

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

export function BreadcrumbWithEllipsis() {
  return (
    <Breadcrumb>
      <BreadcrumbList>
        <BreadcrumbItem><BreadcrumbLink href="/docs">Home</BreadcrumbLink></BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem><BreadcrumbEllipsis /></BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem><BreadcrumbLink href="/docs/get-started">Settings</BreadcrumbLink></BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem><BreadcrumbPage>Members</BreadcrumbPage></BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  )
}

```

### Workspace path [#workspace-path]

Small size and compact spacing suit app chrome, above content or inside a header row.

```tsx
import { House as HomeIcon } from "honestui/icons";

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

export function BreadcrumbWorkspacePath() {
  return (
    <Breadcrumb>
      <BreadcrumbList>
        <BreadcrumbItem>
          <BreadcrumbLink href="/docs" aria-label="Documentation home">
            <HomeIcon aria-hidden="true" className="size-4" />
          </BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbLink href="/docs/get-started">Workspace</BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbLink href="/docs/components/card">Design</BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem>
          <BreadcrumbPage>Tokens</BreadcrumbPage>
        </BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  );
}

```

### Compact size [#compact-size]

`text-xs` plus short labels for tight layouts where even `small` would crowd.

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

export function BreadcrumbCompactPath() {
  return (
    <Breadcrumb>
      <BreadcrumbList className="text-xs">
        <BreadcrumbItem><BreadcrumbLink href="/docs">Docs</BreadcrumbLink></BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem><BreadcrumbLink href="/docs/components/button">Components</BreadcrumbLink></BreadcrumbItem>
        <BreadcrumbSeparator />
        <BreadcrumbItem><BreadcrumbPage>Breadcrumb</BreadcrumbPage></BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  )
}

```

### Bullet separator [#bullet-separator]

A quieter dot reads well when chevrons feel heavy at small sizes.

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

function BulletSeparator() {
  return (
    <span className="inline-flex h-0.5 w-2 shrink-0 rounded-full bg-foreground/30" />
  )
}

export function BreadcrumbBulletSeparator() {
  return (
    <Breadcrumb>
      <BreadcrumbList>
        <BreadcrumbItem>
          <BreadcrumbLink href="/docs">Home</BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator className="flex items-center">
          <BulletSeparator />
        </BreadcrumbSeparator>
        <BreadcrumbItem>
          <BreadcrumbLink href="/docs/components/button">Components</BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbSeparator className="flex items-center">
          <BulletSeparator />
        </BreadcrumbSeparator>
        <BreadcrumbItem>
          <BreadcrumbPage>Breadcrumb</BreadcrumbPage>
        </BreadcrumbItem>
      </BreadcrumbList>
    </Breadcrumb>
  )
}

```

### Inside a Card [#inside-a-card]

Framing a breadcrumb in card chrome keeps context visible without adding a full header.

```tsx
import { House as HouseIcon } from "honestui/icons"

import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/honest-ui/ui/breadcrumb"
import { Card, CardContent } from "@/components/honest-ui/ui/card"

export function BreadcrumbCard() {
  return (
    <Card className="p-2">
      <CardContent className="px-1 py-0">
        <Breadcrumb>
          <BreadcrumbList className="gap-1.5 sm:gap-1.5">
            <BreadcrumbItem>
              <BreadcrumbLink href="/docs" className="flex items-center gap-1.5">
                <HouseIcon className="size-4" aria-hidden="true" />
                Home
              </BreadcrumbLink>
            </BreadcrumbItem>
            <BreadcrumbSeparator />
            <BreadcrumbItem>
              <BreadcrumbLink href="/docs/components/card">Products</BreadcrumbLink>
            </BreadcrumbItem>
            <BreadcrumbSeparator />
            <BreadcrumbItem>
              <BreadcrumbPage className="font-semibold">
                Checkout
              </BreadcrumbPage>
            </BreadcrumbItem>
          </BreadcrumbList>
        </Breadcrumb>
      </CardContent>
    </Card>
  )
}

```

## API reference [#api-reference]

`Breadcrumb` accepts native `<nav>` props and adds:

| Prop   | Values                | Default    |
| ------ | --------------------- | ---------- |
| `size` | `"small"`, `"medium"` | `"medium"` |

The list renders an `<ol>`, items render `<li>` elements, and each part forwards its matching native element props. `BreadcrumbPage` renders a `<span>` with `role="link"`, `aria-current="page"`, and `aria-disabled="true"`, expressing "this is where you are, not somewhere you can go." `BreadcrumbSeparator` renders a `role="presentation"`, `aria-hidden` list item containing a chevron by default. `BreadcrumbEllipsis` hides its icon and announces "More".

`BreadcrumbLink`, `BreadcrumbDropdownTrigger`, and `BreadcrumbDropdownItem` accept Base UI `render` props so they can compose with your router and menu components.

There is no loading or error state; a breadcrumb is static structure derived from the current route.
