Breadcrumbs
Breadcrumbs show where the current page sits in the hierarchy and link back to each parent page. Use them on pages more than one level deep, such as a lesson inside a space.
Beta until breadcrumb links get a designed focus state (Figma has none, as C10), and a collapsed pattern for long trails are decided. Both are pending a decision. Everything else on this page is implemented and tested.
Preview
Change the controls to try every supported combination. The preview renders the real component and the code updates with it. Links are live but don't leave the page.
import { Breadcrumb, BreadcrumbItem } from '@nuton/ui'
import { RiHomeSmile2Line } from '@remixicon/react'
<Breadcrumb label="Playground location">
<BreadcrumbItem href="/" icon={<RiHomeSmile2Line />}>Home</BreadcrumbItem>
<BreadcrumbItem href="/spaces/">Spaces</BreadcrumbItem>
<BreadcrumbItem href="/spaces/biology-101/">Biology 101</BreadcrumbItem>
<BreadcrumbItem href="/spaces/biology-101/lessons/">Lessons</BreadcrumbItem>
<BreadcrumbItem current>Cell structure</BreadcrumbItem>
</Breadcrumb>Installation
Availability
@nuton/ui and @nuton/tokens are private workspace packages. They aren't published to a registry yet (D4: registry deferred; D12: distribution waits on the provenance review). Inside the Nuton monorepo, add them as workspace dependencies. The commands below apply once the packages are published.
1. Add the packages
pnpm add @nuton/ui @nuton/tokens @remixicon/reactnpm install @nuton/ui @nuton/tokens @remixicon/reactyarn add @nuton/ui @nuton/tokens @remixicon/reactPeer dependencies: react and react-dom 18 or later. @remixicon/react supplies the arrow divider and any item icons (D1). Breadcrumb has no client state, so it renders in server components.
2. Load tokens and component styles once
// app/layout.tsx (or your app's entry file)
import '@nuton/tokens/tokens.css' // --nu-* design tokens
import '@nuton/ui/styles.css' // component styles (uses the tokens)Usage
import { Breadcrumb, BreadcrumbItem } from '@nuton/ui'
export function LessonBreadcrumb() {
return (
<Breadcrumb>
<BreadcrumbItem href="/spaces/">Spaces</BreadcrumbItem>
<BreadcrumbItem href="/spaces/biology-101/">Biology 101</BreadcrumbItem>
<BreadcrumbItem current>Cell structure</BreadcrumbItem>
</Breadcrumb>
)
}- List items from the top of the hierarchy down. The last item is the current page: set
currentand leave outhref. - Breadcrumb renders a
<nav>landmark named “Breadcrumb”. Translatelabelin other languages, and give each breadcrumb a differentlabelwhen a page has more than one. - Use the page titles people see in each page's heading, in sentence case: “Biology 101”, “Cell structure”.
- Pass your router's link component through
asso navigation stays client-side.
This site's own header already has a landmark named “Breadcrumb”, so every example on this page passes a different label, which is the second rule above in practice.
Variants and sizes
Breadcrumbs have one size. The divider is set once on Breadcrumb; each item can show text, an icon, or both.
Dividers
<Breadcrumb label="Arrow divider">…</Breadcrumb> // default
<Breadcrumb label="Slash divider" separator="slash">…</Breadcrumb>
<Breadcrumb label="Dot divider" separator="dot">…</Breadcrumb>
// Each one contains:
<BreadcrumbItem href="/spaces/">Spaces</BreadcrumbItem>
<BreadcrumbItem href="/spaces/biology-101/">Biology 101</BreadcrumbItem>
<BreadcrumbItem current>Cell structure</BreadcrumbItem>Arrow is Remix arrow-right-s-line in Icon/Disabled-300, mirrored in right-to-left pages. Slash (“/”) and dot (“•”) are Label/Small text in Text/Disabled-500. You can pass any node as a custom divider, but it isn't in Figma, so prefer the three presets. Dividers are always hidden from screen readers. Use one divider style across a product.
Item content
Figma's Icon and Text options. An icon-only item has no visible label, so it needs aria-label. Figma's default icon is home-smile-2-line, for the first item.
<Breadcrumb label="Icon and text">
<BreadcrumbItem href="/" icon={<RiHomeSmile2Line />}>Home</BreadcrumbItem>
<BreadcrumbItem href="/spaces/">Spaces</BreadcrumbItem>
<BreadcrumbItem current>Biology 101</BreadcrumbItem>
</Breadcrumb>
<Breadcrumb label="Icon only">
<BreadcrumbItem href="/" icon={<RiHomeSmile2Line />} aria-label="Home" />
<BreadcrumbItem href="/spaces/">Spaces</BreadcrumbItem>
<BreadcrumbItem current>Biology 101</BreadcrumbItem>
</Breadcrumb>
<Breadcrumb label="Text only">
<BreadcrumbItem href="/">Home</BreadcrumbItem>
<BreadcrumbItem href="/spaces/">Spaces</BreadcrumbItem>
<BreadcrumbItem current>Biology 101</BreadcrumbItem>
</Breadcrumb>Sizes
| Part | Size | Notes |
|---|---|---|
| Row | 20 high | Label/Small (14/20) |
| Gap | 6 | Between every item and divider, and between an icon and its label |
| Row gap when wrapped | 4 | Code-only; Figma shows one row |
| Item icon | 20 × 20 | Icon/Sub-600; Icon/Strong-950 on hover and for the current page |
| Divider | 20 × 20 | Arrow icon, or the slash or dot centred in the box |
| Link hit area | content + 4 wide, 24 high | An invisible 2px extension on every side; the layout keeps Figma's 20px row |
| Underline offset | 2 | Link labels are underlined, as in Figma; the current page isn't (C43) |
Sizes come from the component CSS, which follows Figma's geometry. Figma's text-only item has an 8px gap, which has no visible effect, so code uses 6 throughout.
Long trails
On narrow screens the trail wraps onto more lines instead of scrolling the page. A single label longer than the line is cut short with an ellipsis. This preview limits the width so the trail wraps.
<Breadcrumb label="Long trail">
<BreadcrumbItem href="/" icon={<RiHomeSmile2Line />} aria-label="Home" />
<BreadcrumbItem href="/spaces/">Spaces</BreadcrumbItem>
<BreadcrumbItem href="/spaces/biology-101/">Biology 101</BreadcrumbItem>
<BreadcrumbItem href="/spaces/biology-101/lessons/">Lessons</BreadcrumbItem>
<BreadcrumbItem current>Photosynthesis and cellular respiration</BreadcrumbItem>
</Breadcrumb>Gap: no collapsed trail (pending a decision)
Figma shows 3–5 items and has no collapsed or ellipsis state, so none is built. Deep trails wrap instead. A designed pattern, such as “Home / … / Lessons / Cell structure” with a menu for the hidden items, is pending a decision.
States and interactions
aria-current="page".
aria-current="page" and no underline, so it doesn't look like a link. Figma underlines it too; code drops that underline (C43).There is no disabled state: a page people can't open shouldn't be in the trail.
State tokens
Set on .nu-breadcrumb. Override them on a wrapper only for a documented exception.
| Token | Value | Used for |
|---|---|---|
--nu-breadcrumb-fg | Text/Sub-600 | Link label |
--nu-breadcrumb-fg-hover | Text/Strong-950 | Link label on hover |
--nu-breadcrumb-fg-current | Text/Strong-950 | Current page label |
--nu-breadcrumb-icon | Icon/Sub-600 | Item icon |
--nu-breadcrumb-icon-hover | Icon/Strong-950 | Item icon on hover |
--nu-breadcrumb-icon-current | Icon/Strong-950 | Current page icon |
--nu-breadcrumb-separator | Icon/Disabled-300 | Arrow divider |
--nu-breadcrumb-separator-text | Text/Disabled-500 | Slash, dot and custom dividers |
--nu-breadcrumb-gap · --nu-breadcrumb-row-gap | --nu-space-1-5 (6px) · --nu-space-1 (4px) | Space between items, and between wrapped rows |
--nu-breadcrumb-icon-size | 20px | Item icon and divider box |
Composition examples
Lesson page header
The breadcrumb sits above the page title and the page's own tabs. It shows where the lesson lives; the tabs switch views inside it.
The preview stands in a paragraph for the <h1>, because this page already has one.
Router links
Pass your router's link through as. It receives href, className, aria-current and the other props, so styling and semantics are unchanged.
import Link from 'next/link'
import { Breadcrumb, BreadcrumbItem } from '@nuton/ui'
<Breadcrumb label="Student location">
<BreadcrumbItem as={Link} href="/spaces/biology-101/">Biology 101</BreadcrumbItem>
<BreadcrumbItem as={Link} href="/spaces/biology-101/students/">Students</BreadcrumbItem>
<BreadcrumbItem current>Progress report</BreadcrumbItem>
</Breadcrumb>Built from route data
Most apps build the trail from the route. Give each item a stable key, and mark only the last one as current.
const trail = [
{ href: '/spaces/', title: 'Spaces' },
{ href: '/spaces/biology-101/', title: 'Biology 101' },
{ href: '/spaces/biology-101/resources/', title: 'Resources' },
]
<Breadcrumb label="Resource location">
{trail.map((page, i) =>
i === trail.length - 1
? <BreadcrumbItem key={page.href} current>{page.title}</BreadcrumbItem>
: <BreadcrumbItem key={page.href} href={page.href}>{page.title}</BreadcrumbItem>,
)}
</Breadcrumb>API reference
Props
Breadcrumb: the navigation landmark and its list.
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | — | BreadcrumbItem elements, top of the hierarchy first. Any number works; Figma shows 3–5. |
separator | 'arrow' | 'slash' | 'dot' | ReactNode | 'arrow' | Divider between items (Figma Divider). A node gives a custom divider. Always aria-hidden. |
label | string | 'Breadcrumb' | Accessible name of the <nav>. Translate it, and make it unique when a page has more than one breadcrumb. |
ref | Ref<HTMLElement> | — | Forwarded to the <nav>. |
…rest | HTMLAttributes<HTMLElement> | — | Any other attribute for the <nav>. className is merged with .nu-breadcrumb. |
BreadcrumbItem: one page in the trail.
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | — | Link target. Without it the item renders a <span>, which is right for the current page. |
current | boolean | false | Marks the current page (Figma Active): sets aria-current="page" and the strong colour. |
icon | ReactNode | — | Icon before the label (Figma Icon, Pick Icon). Rendered aria-hidden. |
as | ElementType | 'a' | Link component to render instead of <a>, such as a router's Link. Used only when href is set. |
children | ReactNode | — | The label (Figma Edit Text). Leave it out for an icon-only item and pass aria-label; a non-link icon-only item turns it into visually hidden text. |
ref | Ref<HTMLElement> | — | Forwarded to the link or <span>, not the <li>. |
…rest | AnchorHTMLAttributes<HTMLElement> | — | Any other link attribute (aria-label, target, onClick…). className is merged with .nu-breadcrumb__link. |
Types are exported as BreadcrumbProps, BreadcrumbItemProps and BreadcrumbSeparator.
Styling hooks
Breadcrumb renders nav.nu-breadcrumb with data-separator (arrow, slash, dot or custom), an ol.nu-breadcrumb__list, and li.nu-breadcrumb__separator between items. Each item is li.nu-breadcrumb__item around .nu-breadcrumb__link, with data-current and data-icon-only. Use props rather than styling these; the --nu-breadcrumb-* tokens (see State tokens) are the supported override points.
Accessibility
Keyboard
| Key | Behaviour |
|---|---|
| Tab | Moves through the links in order. The current page has no link, so it isn’t a tab stop. |
| Enter | Follows the focused link (native behaviour). |
Screen readers
A nav landmark named by label, with an ordered list, so the number of levels is announced. The current page has aria-current="page". Dividers and icons are hidden; icon-only items are named by aria-label.
Focus
Figma has no focus state, so the browser outline is kept, 2px clear of the label (pending a decision, as C10).
Contrast and colour
Link labels are 6.69:1 and the current page 17.93:1. Dividers are 1.53:1, but they're decorative and hidden, so they're exempt. Hover changes only the colour, but every link is also underlined.
Touch and pointer
The row is 20px, as in Figma, but each link's hit area extends to 24px high without moving anything (WCAG 2.5.8). Hover styles apply only on hover-capable pointers.
You must: mark the last item current and leave out its href; give icon-only items an aria-label; give each breadcrumb on a page a unique, translated label.
Figma mapping
| Figma property | Figma values | Code |
|---|---|---|
| Group: Divider | Arrow · Slash · Dot | separator: arrow · slash · dot |
| Group: Quantity | 03 · 04 · 05 | The number of BreadcrumbItem children (any number) |
| Item: State | Default · Hover · Active | Interactive states; Active is current |
| Item: Icon · Pick Icon | Off · On; home-smile-2-line | icon |
| Item: Text · Edit Text | Off · On; text | children (leave out and pass aria-label for Text off) |
Nodes: 292:18369 (page), 292:18404 (group), 292:18382 (item). The group binds remote colour and text styles; code uses the local tokens with the same values (the C19 and C22 principle).
Guidelines and related components
Use Breadcrumbs for
- Pages two or more levels deep, such as a lesson inside a space.
- Letting people jump back to any parent page in one step.
- Showing context when people arrive from search or a shared link.
Do and don't
Common mistakes
- Forgetting
currenton the last item, so screen readers can't tell which page is open. - Leaving two breadcrumbs on one page with the same “Breadcrumb” label, which makes their landmarks indistinguishable.
- Using labels that don't match the page headings, so people can't connect the trail to the pages.
- Wrapping
BreadcrumbItemin your own<li>or link. It already renders both; useasfor a router link.