---
title: "Breadcrumbs"
description: "Where a page sits in a hierarchy of pages, and the way back up it. The levels above are links, in order from the top, and the page itself is last."
url: "https://tanrei.chnotchy.com/components/breadcrumbs/"
last_updated: 2026-10-23T21:17:12.000Z
---

# Breadcrumbs

> Where a page sits in a hierarchy of pages, and the way back up it. The levels above are links, in order from the top, and the page itself is last.

## Overview

A trail shows where a page is in the hierarchy, not the way a person came to it. Use it where pages sit inside other pages, three or more levels deep.

### Usage

On the web the breadcrumbs are an element around your own anchors. The anchors stay in the document, so they are links before any script runs, and a crawler, a router that works on the page’s anchors and a router’s own link component all find them.

A level with a destination is an `<a href>`. A level without one is a `<span>`: the current page, or a level that has no page of its own.

SwiftUI, Compose, Flutter and React Native are planned: the names beside each platform at the top of this page are the ones the spec gives them, and nothing ships there yet.

**Web**

```html
<link rel="stylesheet" href="@tanrei/web/tokens.css" />
<link rel="stylesheet" href="@tanrei/web/icons.css" />
<link rel="stylesheet" href="@tanrei/web/breadcrumbs.css" />

<tanrei-breadcrumbs label="Breadcrumbs" overflow-label="Show hidden levels">
  <a href="/">Home</a>
  <a href="/components/">Components</a>
  <span aria-current="page">Button</span>
</tanrei-breadcrumbs>

<!-- A glyph before the first label, or alone. The text is still the level's name. -->
<tanrei-breadcrumbs label="Breadcrumbs" overflow-label="Show hidden levels">
  <a href="/" data-icon="home" data-icon-only>Home</a>
  <a href="/components/">Components</a>
  <span aria-current="page">Button</span>
</tanrei-breadcrumbs>

<script type="module">
  import '@tanrei/web';
</script>
```

**React**

```tsx
import { Breadcrumbs } from '@tanrei/react';
import '@tanrei/web/breadcrumbs.css';
import { Link as RouterLink } from 'react-router-dom';

<Breadcrumbs label="Breadcrumbs" overflowLabel="Show hidden levels">
  {/* A router's link renders its own anchor, and takes the navigation from its click. */}
  <RouterLink to="/">Home</RouterLink>
  <RouterLink to="/components/">Components</RouterLink>
  <span aria-current="page">Button</span>
</Breadcrumbs>
```

| Prop | On the web |
| --- | --- |
| `label` | the `label` attribute: the name of the navigation landmark |
| `overflow` | `overflow="collapse"` (the default) or `overflow="wrap"` |
| `overflowLabel` | `overflow-label`: the name of the overflow button |
| `underline` | `underline="always"`, `"hover"` or `"never"`. Without it the trail follows the design system |
| a level | an `<a href>` child, or a `<span>` child for a level without a destination |
| `label` of a level | its text |
| `current` | `aria-current="page"` on the last child |
| `icon` | `data-icon="home"` |
| `iconOnly` | `data-icon-only` |
| `click` | the anchor’s own `click` |

## Specs

- **A level that leads somewhere is underlined** in the default system. There the underline, not the color, says that a level is a link, as it does for a link in a sentence. A level without a destination is secondary text. The current page is heavier, in the text color, and never underlined.
- **When the underline is drawn is a choice.** `underline="hover"` draws it only while a level is hovered, focused from the keyboard or pressed. `underline="never"` draws none. Without the attribute the trail follows `comp.breadcrumbs.underline.visibility`, which follows the design system’s choice for every text link. Choose `hover` or `never` only where the link color stands apart from the text color. Forced colors always draw the line.
- **The current page is text.** A link to the page a person is on leads nowhere and adds a tab stop to every page. Give it `href` only where the page’s views change under one address. It is then a link with the same look. Where the page’s heading stands right under the trail, you may leave the current page out.
- **Separators are the component’s**: `chevron_right` between two neighbours. Never write a separator into a label.
- **A long trail folds its middle.** Under `collapse` the trail stays on one line. Levels go behind the overflow button from the one next to the first, only as many as the width asks for, then the first. The last level is never taken out. Its label is cut only when the button and it alone do not fit.
- **The overflow button opens a menu** of the levels taken out, in the trail’s order. A level with a destination is a link there too. A row of the menu is never underlined.
- **`wrap` keeps every level** and lets the trail run over several lines. A line never begins with a separator, and no label is cut.
- **It folds by its own width**, not the window’s, and decides again when its width, a label or the text size changes.
- **Right to left**, the trail runs from the right and the separators point left. A left-to-right name keeps its own order.

- Category: Navigation
- Level: base
- Listing: default
- Page: https://tanrei.chnotchy.com/components/breadcrumbs/
- Spec: https://github.com/chnotchy/tanrei/blob/main/specs/breadcrumbs.spec.json

### Availability

| Design system | Has it | Notes |
| --- | --- | --- |
| Tanrei (`tanrei`) | Yes | — |
| Cupertino (Apple HIG-inspired) (`cupertino`) | Yes | — |
| M3 Expressive-like (Material 3 Expressive-inspired) (`m3-expressive-like`) | Yes | — |
| M3-like (Material 3-inspired) (`m3-like`) | Yes | — |

### Platforms

- Web: the element `<tanrei-breadcrumbs>`, registered by importing `@tanrei/web` (or only its own module, `@tanrei/web/components/breadcrumbs`).
- React: `Breadcrumbs` from `@tanrei/react`.

| Platform | Name | Status |
| --- | --- | --- |
| Web | `tanrei-breadcrumbs` | lagging |
| SwiftUI | `TanreiBreadcrumbs` | planned |
| Jetpack Compose | `TanreiBreadcrumbs` | planned |
| Flutter | `TanreiBreadcrumbs` | planned |
| React Native | `Breadcrumbs` | planned |

Names a platform gives in its own idiom:

| Spec | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
| --- | --- | --- | --- | --- | --- |
| `label` (prop) | `label` | `label` | `contentDescription` | `semanticLabel` | `accessibilityLabel` |
| `overflowLabel` (prop) | `overflowLabel` | `overflowLabel` | `overflowContentDescription` | `overflowSemanticLabel` | `overflowAccessibilityLabel` |

Statuses: `planned` — In the inventory. Nothing shippable exists yet. `lagging` — An implementation of an earlier spec exists; it has not caught up with the current one. `alpha` — Implemented with tests. The API may still change. `beta` — Feature complete and at parity. Only fixes are expected. `stable` — Covered by semantic versioning. `deprecated` — Still shipped; scheduled for removal.

### Anatomy

- `root`: The navigation landmark and, in it, one ordered list of the levels, from the top of the hierarchy to the page. On one line by default.
- `container`: The shape behind a level that is a link, and behind the overflow button. It has no fill at rest and takes no room in the layout: it is where hover, focus and press are drawn.
- `label`: A level's text, in the trail's text style.
- `underline`: The line under the label of a level that is a link, in the label's color. When it is drawn is a choice (BRC-30): at rest in the default system, where it is what says that a level leads somewhere. The current page and a level without a destination have none.
- `icon` (optional): A glyph of the system's icon set that the product names for a level (a house for the top), before the label or in its place. Decorative.
- `separator`: The glyph chevron_right between two neighbouring levels, drawn by the trail. Decorative; mirrored in a right-to-left layout by the icon helper.
- `overflow-button` (optional): An icon-only button showing more_horiz that stands where levels were taken out of a trail too long for its width, and opens a menu of them.
- `menu` (optional): The menu of the levels that were taken out, in the trail's order: a Tanrei menu, opened from the overflow button.
- `focus-ring`: The indicator shown for keyboard focus, around a link's or the overflow button's container.

### Props

| Prop | Type | Default | Web attribute | Description |
| --- | --- | --- | --- | --- |
| `label` | `string` | `""` | `label` | The accessible name of the landmark ("Breadcrumbs"), in the product's language. Tanrei ships no words of its own. It tells this landmark from the page's other navigation, so it is required. |
| `overflow` | `enum`: `collapse`, `wrap` | `"collapse"` | `overflow` | What a trail longer than its width does. collapse: it stays on one line and its middle levels go behind the overflow button, as many as the width asks for (BRC-9). wrap: every level stays in the trail, which runs over as many lines as it needs (BRC-15). |
| `overflowLabel` | `string` | `""` | `overflow-label` | The accessible name of the overflow button ("Show hidden levels"), in the product's language. Required while overflow is collapse; ignored while it is wrap. |
| `underline` | `enum`: `auto`, `always`, `hover`, `never` | `"auto"` | `underline` | When the levels that are links are underlined. always: at rest and in every state. hover: while a level is hovered, focused from the keyboard or pressed, and not at rest. never: in no state. Auto takes comp.breadcrumbs.underline.visibility, the design system's answer (always in the default system). It applies to every level of the trail. |

### Item: `item`

One level of the hierarchy: a link to it, the page the person is on, or the name of a level that has no page.

Names: Web `a`, SwiftUI `TanreiBreadcrumbsItem`, Jetpack Compose `TanreiBreadcrumbsItem`, Flutter `TanreiBreadcrumbsItem`, React Native `BreadcrumbsItem`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | `""` | The level's name: the title of its page, as short as the product has it. It is the item's accessible name, drawn or not. |
| `href` | `string` | `""` | The destination: an address as the platform's links take one. With one the level is a link; without one it is text — the current page, or a level that has no page of its own (BRC-3). |
| `current` | `boolean` | `false` | The level is the page the person is on. Only the last item may be current. It is exposed as the current page and drawn heavier, without an underline; give it no href, so that it is text and not a link to where the person already is (BRC-4). |
| `icon` | `string` | `""` | The name of a glyph of the system's icon set, drawn before the label ("home" for the top of the hierarchy). Empty: no glyph. |
| `iconOnly` | `boolean` | `false` | Draws the glyph alone and keeps the label as the accessible name. Ignored while icon is empty. |

| Event | Description |
| --- | --- |
| `click` | A level that is a link was activated by pointer, touch, Enter or assistive technology, in the trail or in the menu. It is emitted before the destination opens; a product that takes the navigation itself (a client-side router) cancels it (BRC-22). |

### States

`enabled`, `hovered`, `focused`, `pressed`

### Accessibility

- Role: `navigation`
- Labeling: The landmark is named by label, which is required. Each level is named by its label, drawn or not (iconOnly); its glyph is decorative. The current page carries the current state; nothing is added to its name. The overflow button is named by overflowLabel, which is required while overflow is collapse, and says that it opens a menu. Separators are hidden from assistive technology.
- Minimum target size: yes

| Keys | Action |
| --- | --- |
| Tab | Moves focus through the levels that are links and the overflow button, in the trail's order: each is a tab stop, as the links of a navigation region are. Text levels and levels behind the button are passed. |
| Enter | Follows the focused link; on the overflow button, opens the menu. Space does what it does on any link (on the web it scrolls the page). |
| Space | On the overflow button, opens the menu. |
| Arrow keys, Home, End, Escape | In the open menu, as the menu's spec says: move among the hidden levels; Escape closes it and returns focus to the button. |

- The current page is text by default, not a link. A link to the page a person is on leads nowhere, and adds a tab stop to every page of a product; as text it is still read in its place in the list, with the current state. A product that needs it to be a link (a page whose views change under one address) gives the item a destination, and it is then a link with the same look (BRC-4). A product whose page heading stands right under the trail may leave the current page out: the trail then ends at the parent, and no item is current.
- In the default system a level that is a link is underlined, and the current page and a level without a page are not. That difference, with the heavier weight of the current page, is what tells them apart; color is not asked to (WCAG 1.4.1). The link color there is the primary's, which no scheme holds apart from the text color, so it could not.
- A design system or a product may choose hover or never for the trail (BRC-30). A level that is a link is then told from a level that is text by its color, so the build holds the link color 3:1 apart from comp.breadcrumbs.label.color in every scheme where the choice is not always (an error in the default system, a warning in a product's layer). A trail whose every level above the current page is a link has no text level to be confused with, and the current page stays heavier; the check holds all the same, because one level without a page is enough. Forced colors draw the underline whatever was chosen (BRC-24).
- The trail is a list of links in a landmark, not a toolbar: every link is a tab stop, and the arrow keys do nothing outside the menu.
- Collapsing hides nothing from a person: every level taken out is in the menu, one press away, and the button says that it opens a menu. A trail that should never hide a level sets overflow to wrap. On the web the hidden levels' anchors stay in the document (BRC-13).
- The last label is cut only when the button and it alone do not fit (BRC-14). The current page is usually the heading right under the trail, so the cut text is on the page in full; a product where it is not uses wrap.
- The target. A level is a link that stands by itself, so its hit area is extended to sys.target without changing what is drawn (BRC-23), bounded halfway to its neighbours. Short labels beside each other keep the spacing WCAG 2.5.8 asks: a separator with its two gaps is 24 wide under the default system.
- Text must remain readable at 200% text size: the trail grows with its text and collapses or wraps by the width it has; nothing has a fixed height (BRC-25).
- Contrast in the default system: sys.color.link, on-surface and on-surface-variant are each held to 4.5:1 (7:1 in the high-contrast schemes) on every surface by the token invariants; the overflow glyph and the separators are on-surface-variant. The focus ring is held to 3:1 against every surface.
- How a state looks is a value decision of the active design system (a state-layer mix in Tanrei, a dim or no hover at all elsewhere). The trail reads the state's tokens and never computes the effect itself.
- A trail shows where a page is in the hierarchy, not the way a person came. A product that publishes the same hierarchy as structured data for search engines writes that itself, from the same items.

### Behaviour

Requirements in EARS form, each with its stable id.

- **BRC-1** THE breadcrumbs SHALL be one navigation landmark named by label that holds one ordered list of its items, in the order the product gave them — from the top of the hierarchy to the page — each a list item with its position in the list, and nothing else that assistive technology reads but the overflow button; WHILE label is empty THE breadcrumbs SHALL still render, and in debug builds report that the landmark has no accessible name. WHILE it has no item THE breadcrumbs SHALL draw nothing, take no room and expose no landmark.
- **BRC-2** WHILE an item's href is a destination and its current is false THE breadcrumbs SHALL draw that level as the platform's own link to it (the web's anchor element in the document itself, not in a shadow root; SwiftUI's Link; on Compose, Flutter and React Native an element with the platform's link semantics whose address is opened by the platform's opener), so that whatever the platform gives a link — on the web its context menu, opening in a new tab or window, copying and dragging its address, the address shown before it is followed, a crawler that reads it, and a link that works before any script has run — is the platform's, and not the trail's to draw or to take over; it SHALL expose the link role, named by the item's label, and SHALL draw the label in comp.breadcrumbs.label.typescale and comp.breadcrumbs.item.content.color and, WHILE its underline is shown (BRC-30), with an underline comp.breadcrumbs.underline.width thick, comp.breadcrumbs.underline.offset below the text's baseline, in the label's color of its state, under the text only and not under a glyph. In the default system the underline is shown at rest and in every state: there, in a trail as in a sentence, the underline and not the color says that a level leads somewhere.
- **BRC-3** WHILE an item has no destination and its current is false THE breadcrumbs SHALL draw that level as text — its label in comp.breadcrumbs.label.typescale and comp.breadcrumbs.label.color, with no underline and no container — which is not focusable, answers no hover, focus or press, has no role of its own and emits nothing: it names a level of the hierarchy that has no page. IF an item's href names a script to run rather than a place to go (the web's javascript: scheme) or cannot be read as an address by the platform THE breadcrumbs SHALL draw that level so too, and in debug builds report it.
- **BRC-4** WHILE the last item's current is true THE breadcrumbs SHALL expose that level as the current page (on the web aria-current with the value page; on a platform without that state, the selected state) and SHALL draw its label in comp.breadcrumbs.current.content.color, with no underline, at the emphasized weight of its text style — that style's weight plus sys.typescale.emphasis.body — so that it is told from the levels above it by more than its color. WHILE that item has no destination it SHALL be text as BRC-3 says: not focusable, without states and without an event. WHILE it has a destination it SHALL be a link as BRC-2 says that keeps this look and answers hover, focus and press with the tokens of the current part. IF current is true on an item that is not the last THE breadcrumbs SHALL draw that item as if current were false, and in debug builds report it.
- **BRC-5** THE breadcrumbs SHALL open a destination in the context the person is in and in no other, SHALL draw no mark for a new context and SHALL draw no state for a destination already visited: the levels of one hierarchy replace the page the person is on, and a level's colors say what it is and what state it is in.
- **BRC-6** THE breadcrumbs SHALL draw one separator between every two neighbouring entries of the trail — levels and the overflow button alike — and none before the first or after the last: the glyph chevron_right of the system's icon set in a box comp.breadcrumbs.separator.size large, in comp.breadcrumbs.separator.color, comp.breadcrumbs.separator.gap from the entry on either side, centred on the line of the label beside it, drawn through the platform's icon helper with sys.icon.family.unselected and sys.icon.fill.unselected, sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max. A separator SHALL be hidden from assistive technology, SHALL be no part of a level's name, link or hit area, and SHALL keep its color in every state of the levels beside it.
- **BRC-7** WHILE an item's icon names a glyph THE breadcrumbs SHALL draw it before the label in the reading direction, in a box comp.breadcrumbs.icon.size large, comp.breadcrumbs.container.gap from the label, centred on the label's line, in the color of the level's label and state, through the icon helper as BRC-6 draws a separator, hidden from assistive technology. WHILE iconOnly is also true THE breadcrumbs SHALL draw the glyph alone, with no label and no underline, its container a square one line of the trail's text high around it, and SHALL keep the item's label as its accessible name. WHILE an item's label is empty THE breadcrumbs SHALL still render it, and in debug builds report that the level has no name.
- **BRC-8** WHILE overflow is collapse THE breadcrumbs SHALL set the trail on one line, each label on one line, as wide as its entries need and no wider than the layout gives it, aligned to the start, and SHALL NOT wrap or scroll it.
- **BRC-9** WHILE overflow is collapse and the trail is wider than the width the layout gives it THE breadcrumbs SHALL take levels out of the trail and draw the overflow button once, in the place of the first level it took out: first the levels between the first and the last, beginning with the one next to the first and going on toward the last, each only if what is left still does not fit; then, if the first level, the button and the last level still do not fit, the first level as well, so that the button begins the trail. It SHALL NOT take out the last level, SHALL take out no more levels than the width asks for, and SHALL measure the trail with its separators and gaps, by the width it is given and not by the window's.
- **BRC-10** THE breadcrumbs SHALL draw the overflow button as the glyph more_horiz of the system's icon set in a box comp.breadcrumbs.icon.size large, in comp.breadcrumbs.overflow.content.color, drawn as BRC-6 draws a separator, in a container that is a square one line of the trail's text high with corners comp.breadcrumbs.container.shape; it SHALL be one list item of the trail holding one focusable element with the button role, named by overflowLabel, that says it opens a menu and whether that menu is open, and WHILE overflowLabel is empty SHALL still render and in debug builds report that the button has no accessible name. WHILE no level is taken out THE breadcrumbs SHALL draw no overflow button.
- **BRC-11** WHEN the user activates the overflow button by pointer, touch, Enter or Space THE breadcrumbs SHALL open a menu anchored to it, below it with the start edges aligned, that lists the levels taken out in the trail's order, each named by its label and showing its glyph where it has one: a level with a destination as an item that is a link to it, a level without one as a disabled item. What the menu draws, how focus enters it and moves in it, and how it closes are the menu's own (its spec's behaviours), and WHEN it closes without a level having been followed THE breadcrumbs SHALL return focus to the overflow button.
- **BRC-12** WHEN the width the layout gives the trail, a label, the text size or the layout direction changes THE breadcrumbs SHALL decide again which levels are taken out, at once and without a motion of its own; a level that fits again SHALL return to its place in the trail. WHEN a level that holds keyboard focus is taken out THE breadcrumbs SHALL move focus to the overflow button, and WHEN the overflow button is removed while it holds focus or its menu is open THE breadcrumbs SHALL close the menu and move focus to the first level that returned.
- **BRC-13** WHILE a level is taken out of the trail THE breadcrumbs SHALL keep it out of the list assistive technology reads and out of the focus order, reached through the menu alone; WHERE the platform's links are elements of a document (the web) THE breadcrumbs SHALL keep that level's link in the document, hidden, so that the whole trail is there for whatever reads the page as built and before any script has run.
- **BRC-14** WHILE overflow is collapse and the overflow button with the last level alone is still wider than the width the layout gives, or the trail has one level that is, THE breadcrumbs SHALL cut that level's label with an ellipsis at the end of its own direction (BRC-28), keeping the button, the separator and a glyph whole; the full label SHALL remain what assistive technology reads.
- **BRC-15** WHILE overflow is wrap THE breadcrumbs SHALL keep every level in the trail and draw no overflow button: it SHALL break the trail onto as many lines as it needs, comp.breadcrumbs.row-gap apart and aligned to the start, only after a separator, so that a separator ends a line and never begins one; a label longer than a line SHALL wrap where its language allows a break and, for a run with no such place, inside the run, and SHALL NOT be cut.
- **BRC-16** WHILE a level that is a link or the overflow button is hovered, keyboard-focused or pressed THE breadcrumbs SHALL render its container, its content — the label with its underline and glyph, or the button's glyph — and its opacity from the component tokens of its part (item, current, overflow) in the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state. The container SHALL be the box of the content, with corners comp.breadcrumbs.container.shape, and SHALL take no room and move nothing beside it.
- **BRC-17** WHEN a level that is a link or the overflow button receives keyboard focus THE breadcrumbs SHALL show the focus ring around its container without animation — the system's focus indicator, sys.focus.offset from the container, sys.focus.outer-width wide in the focus-ring color, with the inner stroke of sys.focus.inner-width where the system has one — and, for a label that runs over several lines of a wrapped trail, around its part of each line; focus gained by pointer or touch SHALL NOT show it.
- **BRC-18** WHEN a level that is a link is pressed by pointer, touch or Enter, or the overflow button by pointer, touch, Enter or Space, THE breadcrumbs SHALL show the pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner.
- **BRC-19** WHILE pressed THE breadcrumbs SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole control and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; content, opacity and shape still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.breadcrumbs.motion.feedback-duration with motion.state-easing, fades over motion.state-duration with motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
- **BRC-20** THE breadcrumbs SHALL ease its container corners by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show its keyboard focus as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README.
- **BRC-21** THE breadcrumbs SHALL change a level or the overflow button between its states over comp.breadcrumbs.motion.state-duration with comp.breadcrumbs.motion.state-easing, and at once where motion is reduced.
- **BRC-22** WHEN the user activates a level that is a link in the trail by pointer, touch or Enter THE breadcrumbs SHALL emit that item's click exactly once and then open its href, unless the product cancelled that click, in which case it SHALL open nothing: the product's own router has taken the navigation. It SHALL NOT hold the navigation back for sys.motion.duration.press-hold. THE breadcrumbs SHALL NOT activate a link in the trail on Space, which on the web scrolls the page as it does on any link, and SHALL NOT show the pressed state for it; a level listed in the menu is the menu's link item, whose keys and activation are the menu's own (MNU-53, MNU-54: its click is emitted once and can be cancelled, the menu closes, then the destination opens, and Space activates it as it does every row of a menu); WHERE the platform gives its links other keys or gestures (on the web a modified or a middle click that opens a new tab, on Apple platforms a long press that previews) THE breadcrumbs SHALL leave them to the platform.
- **BRC-23** WHERE the visual size of a level that is a link or of the overflow button is below the minimum target THE breadcrumbs SHALL extend its hit area on both axes to sys.target.touch for touch input and sys.target.pointer for fine pointers, without changing its visual size or the room it takes, and on the axis of the trail no further than halfway to the entry beside it, so that no two hit areas overlap; a platform that cannot tell the input type SHALL use sys.target.touch.
- **BRC-24** WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE breadcrumbs SHALL draw a level that is a link, with its underline and glyph, in the platform's link color, the current page, a level without a destination and the separators in its text color, the overflow button's glyph in its button text color, and the focus ring in its highlight color, and SHALL draw the underline of a level that is a link at rest, whatever underline and the tokens it follows say: the colors are then the person's, and nothing holds a link's color apart from a text level's.
- **BRC-25** WHILE the text size is enlarged THE breadcrumbs SHALL grow with its text — its labels, its containers and its hit areas — keeping comp.breadcrumbs.separator.gap, comp.breadcrumbs.container.gap, comp.breadcrumbs.underline.width, comp.breadcrumbs.underline.offset, comp.breadcrumbs.row-gap and its glyphs at comp.breadcrumbs.icon.size and comp.breadcrumbs.separator.size, and SHALL decide again what fits (BRC-12).
- **BRC-26** THE breadcrumbs SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the breadcrumbs is shown THE breadcrumbs SHALL draw itself for the new direction at once, without a motion of its own, keeping its focus and whether its menu is open.
- **BRC-27** WHERE the layout direction is right-to-left THE breadcrumbs SHALL lay the trail out from the right — the top of the hierarchy at the right and the page at the left, a level's glyph at the right of its label, the menu opening with its right edge on the button's — and SHALL draw each separator mirrored left to right, pointing to the left, the way the trail reads: the icon helper mirrors it inside its own box because the system's icon set marks chevron_right as mirrored. It SHALL draw a level's own glyph mirrored exactly when the icon set marks that glyph's name or the product forces mirroring for that use, and unmirrored when the product forbids it. THE breadcrumbs SHALL NOT mirror a glyph itself, and the reading and focus order SHALL stay from the top of the hierarchy to the page.
- **BRC-28** THE breadcrumbs SHALL lay out each label as text of its own direction, isolated from the text around it: it takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right name, a number with its sign or unit and a version keep their own order in a right-to-left layout and reorder nothing beside them; THE breadcrumbs SHALL still place the label by the layout direction, not by the text's own, and SHALL cut a label that does not fit (BRC-14) at the end of its own direction, so that a left-to-right label in a right-to-left layout loses its right end and never its beginning.
- **BRC-29** WHEN overflow names a value the breadcrumbs does not have THE breadcrumbs SHALL behave as collapse, and in debug builds report the unknown value.
- **BRC-30** THE breadcrumbs SHALL show the underline of every level that is a link as its underline prop names: WHILE underline is auto, as comp.breadcrumbs.underline.visibility names, which follows sys.behavior.link-underline unless a design system sets the trail apart; WHILE underline is always, hover or never, as that value names, whatever the tokens say. always draws the line at rest and in every state; hover draws none at rest and draws it WHILE the level is hovered, keyboard-focused or pressed, the line arriving and leaving with that state as BRC-16 renders it and moving no level; never draws none in any state, and hover, focus and press are still shown by the container, the label color and the focus ring. The links of the overflow menu are menu items and are never underlined. WHEN underline, or the token it follows, changes THE breadcrumbs SHALL draw the other form at once, keeping its focus and what it has collapsed; WHEN underline names a value the breadcrumbs does not define THE breadcrumbs SHALL treat it as auto and in debug builds report the unknown value. A platform that cannot draw a choice SHALL fall back to the first value, always, and state that in its README.
- **BRC-31** WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE breadcrumbs SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on its navigation landmark (BRC-1) — an id reference by the elements it names in the host's tree or a tree around it, a description after its own. WHILE label is not empty it is the landmark's name, over a name given so; WHILE label is empty, a name given so is the landmark's name and SHALL NOT be reported as having none in debug builds. WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE breadcrumbs SHALL take it off that element.

### Tokens

As written in the token documents (DTCG): an alias in braces names the token it takes its value from, which each design system resolves. On the web each token is a CSS custom property: `--tanrei` and the name after `tanrei`, joined by hyphens (`tanrei.comp.breadcrumbs.…` → `--tanrei-comp-breadcrumbs-…`).

#### `tanrei.comp.breadcrumbs`

| Token | Type | Value |
| --- | --- | --- |
| `comp.breadcrumbs.container.shape` | `dimension` | `{tanrei.sys.shape.corner.extra-small}` |
| `comp.breadcrumbs.container.gap` | `dimension` | `{tanrei.sys.space.space50}` |
| `comp.breadcrumbs.label.typescale` | `typography` | `{tanrei.sys.typescale.body}` |
| `comp.breadcrumbs.label.color` | `color` | `{tanrei.sys.color.on-surface-variant}` |
| `comp.breadcrumbs.underline.width` | `dimension` | `{tanrei.sys.border.hairline}` |
| `comp.breadcrumbs.underline.offset` | `dimension` | `{tanrei.sys.space.space25}` |
| `comp.breadcrumbs.underline.visibility` | `number` | `{tanrei.sys.behavior.link-underline}` |
| `comp.breadcrumbs.icon.size` | `dimension` | `{tanrei.sys.icon.extra-small}` |
| `comp.breadcrumbs.separator.size` | `dimension` | `{tanrei.sys.icon.extra-small}` |
| `comp.breadcrumbs.separator.gap` | `dimension` | `{tanrei.sys.space.space50}` |
| `comp.breadcrumbs.separator.color` | `color` | `{tanrei.sys.color.on-surface-variant}` |
| `comp.breadcrumbs.row-gap` | `dimension` | `{tanrei.sys.space.space50}` |
| `comp.breadcrumbs.item.container.color` | `color` | `{"colorSpace":"srgb","components":[0,0,0],"alpha":0,"hex":"#000000"}` |
| `comp.breadcrumbs.item.content.color` | `color` | `{tanrei.sys.color.link}` |
| `comp.breadcrumbs.item.state-layer.color` | `color` | `{tanrei.comp.breadcrumbs.item.content.color}` |
| `comp.breadcrumbs.current.container.color` | `color` | `{"colorSpace":"srgb","components":[0,0,0],"alpha":0,"hex":"#000000"}` |
| `comp.breadcrumbs.current.content.color` | `color` | `{tanrei.sys.color.on-surface}` |
| `comp.breadcrumbs.current.state-layer.color` | `color` | `{tanrei.comp.breadcrumbs.current.content.color}` |
| `comp.breadcrumbs.overflow.container.color` | `color` | `{"colorSpace":"srgb","components":[0,0,0],"alpha":0,"hex":"#000000"}` |
| `comp.breadcrumbs.overflow.content.color` | `color` | `{tanrei.sys.color.on-surface-variant}` |
| `comp.breadcrumbs.overflow.state-layer.color` | `color` | `{tanrei.comp.breadcrumbs.overflow.content.color}` |
| `comp.breadcrumbs.motion.state-duration` | `duration` | `{tanrei.sys.motion.role.state.compact}` |
| `comp.breadcrumbs.motion.state-easing` | `cubicBezier` | `{tanrei.sys.motion.easing.state}` |
| `comp.breadcrumbs.motion.feedback-duration` | `duration` | `{tanrei.sys.motion.role.feedback}` |

Per-state tokens, valued by the design system's pattern rules (not written by hand):

- `comp.breadcrumbs.<variant>.<state>.<property>` for the variants `item`, `current`, `overflow`, the states `hovered`, `focused`, `pressed` and the properties `container.color`, `content.color`, `state-layer.color`.
- `comp.breadcrumbs.<state>.<property>` for the states `hovered`, `focused`, `pressed` and the properties `opacity`, `container.shape`.

### What the web draws differently

- Before the element is defined, and with scripting off, the stylesheet draws the wrap form: every level, with its separators. A trail that fits its width does not move when the element is defined. The landmark, the list and the folding come with the element.
- A level chosen in the menu is one `click` on your own anchor, and the browser then follows that anchor unless you cancelled the click. A router’s link component takes the navigation there exactly as it does in the trail.
- A label that wraps is one box: the focus ring is drawn around the box, not around its part of each line.
- A level with a glyph is laid out in the layout’s direction, so a left-to-right label with a glyph in a right-to-left trail does not take its own direction.
- A level’s glyph and the separators drawn by the stylesheet need `icons.css`. Without it their room is kept and nothing is drawn in it.
- Keep the breadcrumbs outside `.tanrei-prose`, or mark them `tanrei-not-prose`: prose draws its own anchors as text links.

## Guidelines

### Do

- Put the trail above the page’s heading, once per page.
- List the levels from the top of the hierarchy to the page, with the names the pages themselves have.
- Leave a level that has no page of its own as text.

### Don’t

- Don’t use a trail to show the steps a person took. It shows where the page is.
- Don’t mark any level but the last as the current page.
- Don’t open a level in a new tab. The levels of one hierarchy replace the page a person is on.
- Don’t hide levels by hand. Use `overflow="wrap"` where every level must always be shown.

## Accessibility

### What the component handles

- The trail is one navigation landmark that holds one ordered list. Each level is a list item, and each link is the browser’s own link.
- The current page carries `aria-current="page"` as you wrote it. Nothing is added to its name.
- The separators and the glyphs are hidden from assistive technology.
- A level taken out is not read and not in the focus order. It is in the menu, one press away, and its anchor is still in the document.
- The overflow button says that it opens a menu and whether the menu is open.
- A link’s hit area reaches the minimum target without changing what is drawn.
- When a focused level is taken out, focus moves to the overflow button. When the button goes, focus moves to the first level that returned.

### What you need to do

- Name the landmark with `label`, in your product’s language. A page has other navigation, and the name tells this one apart. Tanrei ships no words of its own.
- Name the overflow button with `overflow-label` while `overflow` is `collapse`, or once for the whole product with `setWords({ breadcrumbsOverflowLabel })`.
- Mark the current page with `aria-current="page"`, on the last level only.
- Give a level shown as a glyph alone its text. The text is its name.
