Breadcrumb

Default

Strong

Two levels


  
/*
 * Breadcrumb — block
 *
 * Hierarchical navigation. Uses <nav> + <ol> for semantic correctness.
 * Separator is a chevron (›) rendered via CSS ::before on every item
 * except the first, marked aria-hidden by being a CSS pseudo-element.
 * CSS-only.
 *
 *   <nav class="breadcrumb" aria-label="Breadcrumb">
 *     <ol class="breadcrumb__list">
 *       <li class="breadcrumb__item"><a href="/" class="breadcrumb__link">Home</a></li>
 *       <li class="breadcrumb__item"><a href="/docs" class="breadcrumb__link">Docs</a></li>
 *       <li class="breadcrumb__item" aria-current="page">Components</li>
 *     </ol>
 *   </nav>
 */

@layer malevich.components {
  .breadcrumb {
    font-family: var(--font-body-support-family);
    font-size: var(--font-body-support-size);
    font-weight: var(--font-body-support-weight);
    line-height: var(--font-body-support-line-height);
    letter-spacing: var(--font-body-support-letter-spacing);

    color: var(--color-ink-subtle);
  }

  .breadcrumb__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-gap-elements-s);
  }

  .breadcrumb__item {
    display: inline-flex;
    align-items: center;
    gap: var(--space-gap-elements-s);
  }

  /* Chevron separator. Rendered before every item except the first.
   * aria-hidden by virtue of being a pseudo-element (not in DOM).
   */
  .breadcrumb__item:not(:first-child)::before {
    content: "›";
    color: var(--color-ink-subtle);
    font-weight: 400;
    user-select: none;
  }

  .breadcrumb__link {
    color: var(--color-ink-regular);
    text-decoration: none;
    border-radius: var(--radius-tag);
    padding-block: var(--space-inset-element-s);
    padding-inline: var(--space-inset-element-s);
    transition: color var(--motion-fast) var(--motion-easing-default);
  }

  .breadcrumb__link:hover {
    color: var(--color-ink-strong);
    text-decoration: underline;
  }

  .breadcrumb__link:focus-visible {
    outline: var(--border-width-focus) solid var(--color-accent);
    outline-offset: var(--border-width-hairline);
  }

  /* The current-page item (aria-current="page") gets stronger styling */
  .breadcrumb__item[aria-current="page"] {
    color: var(--color-ink-strong);
    font-weight: 600;
  }

  /* Variants */

  .breadcrumb.-strong {
    color: var(--color-ink-regular);
  }
  .breadcrumb.-strong .breadcrumb__link {
    color: var(--color-ink-strong);
  }
  .breadcrumb.-strong .breadcrumb__item[aria-current="page"] {
    color: var(--color-accent);
  }
}
This component is pure CSS — no JavaScript required.
# Breadcrumb — AGENTS.md

> Auto-generated from `breadcrumb.docs.md` + the modifier applicability matrix.
> Edit those source files; this file regenerates on build.

## Identity

- **Component:** `breadcrumb`
- **Layer:** blocks
- **Status:** stable
- **Last updated:** 2026-05-19

## Summary

- Multi-level navigation where users benefit from knowing where they

## Variants

| Variant | Class                | Use for                                |
|---------|----------------------|----------------------------------------|
| Default | `.breadcrumb`        | Standard, subtle ink                   |
| Strong  | `.breadcrumb.-strong`| Higher-contrast, accent current page   |

## Modifier applicability

Per ADR-0007. Modifiers attach via `data-{category}="value"` attributes.

| Category | Accepts |
|---|---|
| `background` | — |
| `surface` | — |
| `effect` | — |
| `shader` | — |
| `rhythm` | `compact`, `regular` |
| `motion` | any value |
| `behavior` | — |

## Anatomy

```html
<nav class="breadcrumb" aria-label="Breadcrumb">
  <ol class="breadcrumb__list">
    <li class="breadcrumb__item">
      <a class="breadcrumb__link" href="/">Home</a>
    </li>
    <li class="breadcrumb__item">
      <a class="breadcrumb__link" href="/docs">Documentation</a>
    </li>
    <li class="breadcrumb__item" aria-current="page">Components</li>
  </ol>
</nav>
```

The current page is a plain `<li>` (not a link) with
`aria-current="page"`. Authors should NOT make the current page a
link — it would navigate to itself.

## Tokens consumed

### From semantic tier
- `--color-ink-strong` — current page text, link hover
- `--color-ink-regular` — link default
- `--color-ink-subtle` — separator, default ambient text
- `--color-accent` — strong-variant current page, focus ring
- `--space-gap-elements-s` — gap between items
- `--space-inset-element-s` — link padding
- `--radius-tag` — link focus radius
- `--border-width-focus` / `--border-width-hairline`
- `--font-body-support-*` — typography
- `--motion-fast` / `--motion-easing-default` — link hover transition

## Accessibility contract

- The outer element is `<nav aria-label="Breadcrumb">`. Screen readers
  announce it as a navigation landmark.
- The list is `<ol>` (ordered, since position carries meaning).
- The current page uses `aria-current="page"`. Assistive tech reads
  it as "current page".
- The chevron separator is a CSS pseudo-element, not in the DOM, so it
  is excluded from the accessibility tree without needing
  `aria-hidden`.
- Links are real `<a>` elements — full keyboard support, focus ring,
  context menus.

## Guidance

### Do

- Always wrap in `<nav aria-label="Breadcrumb">`.
- Use `<ol>` not `<ul>` — order matters.
- Mark the current page with `aria-current="page"` and render as
  text, not a link.
- Keep labels short — the goal is recognition, not exposition.

### Don't

- Don't make the current page a link.
- Don't include the chevron in the DOM. Let CSS handle it so the
  accessibility tree stays clean.
- Don't use breadcrumbs as the primary navigation. They are a
  secondary "you are here" aid.