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
  • Accessibility
  • Figma Bread Crumbs 292:18369
  • packages/ui/src/components/Breadcrumb

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.

tsx
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

bash
pnpm add @nuton/ui @nuton/tokens @remixicon/react

Peer 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

tsx
// 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

tsx
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 current and leave out href.
  • Breadcrumb renders a <nav> landmark named “Breadcrumb”. Translate label in other languages, and give each breadcrumb a different label when 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 as so 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

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.

Sizes

PartSizeNotes
Row20 highLabel/Small (14/20)
Gap6Between every item and divider, and between an icon and its label
Row gap when wrapped4Code-only; Figma shows one row
Item icon20 × 20Icon/Sub-600; Icon/Strong-950 on hover and for the current page
Divider20 × 20Arrow icon, or the slash or dot centred in the box
Link hit areacontent + 4 wide, 24 highAn invisible 2px extension on every side; the layout keeps Figma's 20px row
Underline offset2Link 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.

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

DefaultText/Sub-600 label and Icon/Sub-600 icon, underlined.
HoverText/Strong-950 and Icon/Strong-950. Shown here statically; applies only to links, on devices with hover.
Current pageFigma's Active state: Text/Strong-950 and Icon/Strong-950. Sets aria-current="page".

FocusFigma defines none (as C10). The browser's outline is kept, 2px clear of the label. Tab here to see it. A designed focus state is pending a decision.
No underline
Current pageStrong-950 text, 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.

TokenValueUsed for
--nu-breadcrumb-fgText/Sub-600Link label
--nu-breadcrumb-fg-hoverText/Strong-950Link label on hover
--nu-breadcrumb-fg-currentText/Strong-950Current page label
--nu-breadcrumb-iconIcon/Sub-600Item icon
--nu-breadcrumb-icon-hoverIcon/Strong-950Item icon on hover
--nu-breadcrumb-icon-currentIcon/Strong-950Current page icon
--nu-breadcrumb-separatorIcon/Disabled-300Arrow divider
--nu-breadcrumb-separator-textText/Disabled-500Slash, 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-size20pxItem icon and divider box

Composition examples

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.

Cell structure (the page's <h1>)

Cells are the basic unit of life.

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.

tsx
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.

tsx
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.

Breadcrumb props
PropTypeDefaultDescription
childrenrequiredReactNode—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.
labelstring'Breadcrumb'Accessible name of the <nav>. Translate it, and make it unique when a page has more than one breadcrumb.
refRef<HTMLElement>—Forwarded to the <nav>.
…restHTMLAttributes<HTMLElement>—Any other attribute for the <nav>. className is merged with .nu-breadcrumb.

BreadcrumbItem: one page in the trail.

BreadcrumbItem props
PropTypeDefaultDescription
hrefstring—Link target. Without it the item renders a <span>, which is right for the current page.
currentbooleanfalseMarks the current page (Figma Active): sets aria-current="page" and the strong colour.
iconReactNode—Icon before the label (Figma Icon, Pick Icon). Rendered aria-hidden.
asElementType'a'Link component to render instead of <a>, such as a router's Link. Used only when href is set.
childrenReactNode—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.
refRef<HTMLElement>—Forwarded to the link or <span>, not the <li>.
…restAnchorHTMLAttributes<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

KeyBehaviour
TabMoves through the links in order. The current page has no link, so it isn’t a tab stop.
EnterFollows 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 propertyFigma valuesCode
Group: DividerArrow · Slash · Dotseparator: arrow · slash · dot
Group: Quantity03 · 04 · 05The number of BreadcrumbItem children (any number)
Item: StateDefault · Hover · ActiveInteractive states; Active is current
Item: Icon · Pick IconOff · On; home-smile-2-lineicon
Item: Text · Edit TextOff · On; textchildren (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.

Don't use Breadcrumbs for

  • Top-level pages. There's nothing to go back to.
  • Browsing history. Breadcrumbs show the hierarchy, not the path someone took.
  • Steps in a flow. Use Progress, or switch views with Tabs.

Do and don't

DoEnd with the current page as plain text. A link to the page you're on goes nowhere and adds a tab stop.
Don'tDon't end with a link to the same page. Screen readers hear a link, and there's no aria-current to say where you are.
DoShow the hierarchy. Each item is a parent of the next, so the trail is the same however people arrived.
Don'tDon't show the path someone clicked. A history trail changes every visit and repeats pages. The Back button already does this.
DoUse an icon only for the first item. A home icon is widely understood; text labels carry the rest.
Don'tDon't add an icon to every item. Repeated icons add noise and make the trail longer without adding meaning.

Common mistakes

  • Forgetting current on 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 BreadcrumbItem in your own <li> or link. It already renders both; use as for a router link.