Français

The Thread, the Maze, and the Stepper

Table of contents

The ball of yarn

Ariadne didn't fight the Minotaur. She did something better: she gave Theseus a thread.

A simple ball of yarn, unspooled at the entrance of the Labyrinth, so he could find his way out after the fight. The hero is Theseus. But without the thread, he would have died in a corridor.

On the web, the labyrinth exists too. A catalogue four levels deep. A checkout tunnel with five steps. An institutional site where every click buries you further into the tree. And when you want to go back, back, back, back, you cross your fingers and hope the browser remembers the right path.

The breadcrumb, on the web, is the same idea: a visible marker that shows you where you are and lets you climb back up without getting lost. Except on the majority of sites, it simply doesn't exist. Not poorly coded, absent. And for someone with anterograde amnesia, who can't retain the path they just took, every page without a breadcrumb is a dead-end corridor.

This article covers two navigation patterns that everyone confuses: the breadcrumb, which shows where you came from in a hierarchy. And the stepper, which shows where you are in a process. Same elements, similar ARIA, opposite intentions.

The problem

Close your eyes and imagine: you're looking for trainers on an e-commerce site. You went through "Shoes", then "Trainers", then "Men", then a "White" filter, then you opened a product page. Five levels deep. Now you want to go back to white trainers to compare another model.

You press "Back". The filter is gone. You press again. You're on the homepage. The path you had in your head, the one you just took, existed nowhere except in your memory.

Now imagine the same situation, but you can't rely on your memory.

For someone with an attention disorder, a single notification was enough to erase the context. For someone using a screen reader, the page is a linear flow with no spatial landmarks. No sidebar, no visual header saying "you are here". For you on mobile, the main navigation is folded into a burger menu three taps away.

The breadcrumb solves this problem in a single line. It doesn't depend on browser history, which is fragile, especially in modern applications that manipulate state. It reflects your position in the site's hierarchy, not the path you took to get there. It's a GPS, not a rear-view mirror.

The breadcrumb

What it is

The breadcrumb is a secondary navigation. It doesn't replace the main menu, it complements it. Its job: show your position in the site's hierarchy and let you jump back up one or more levels in a single click.

You recognise it: that discreet line at the top of the page. "Home > Shoes > Trainers > Air Max 90", that you almost never look at, until you need it. And when you need it, nothing else does the job.

Use a breadcrumb when your site has a hierarchy deeper than two levels. A product catalogue, technical documentation, a departmental intranet. As soon as the user can go deep into the tree, they need a thread to climb back.

Don't use it on a dashboard, a landing page or a flat blog. If your structure is linear or fits on a single level, the breadcrumb adds nothing, it clutters.

Semantic HTML

Four elements are enough. No <div>, no role="navigation", no JavaScript.

breadcrumb.html
<nav aria-label="Breadcrumb">
  <ol>
    <li><a href="/">Home</a></li>
    <li><a href="/shoes">Shoes</a></li>
    <li><a href="/shoes/trainers">Trainers</a></li>
    <li><a href="/shoes/trainers/air-max" aria-current="page">Air Max 90</a></li>
  </ol>
</nav>

Let's break it down.

<nav>: It's a navigation. The browser and screen readers know immediately. aria-label="Breadcrumb" distinguishes it from the main navigation when there are multiple <nav> elements on the page. No need to add role="navigation", that's the implicit role of <nav>, doubling it is redundant.

<ol>: An ordered list, not <ul>. Order matters: "Home" comes before "Shoes" which comes before "Trainers". This hierarchy is semantic. A screen reader announces "list, 4 items" and numbers each one. With <ul>, you lose that information.

<a>: Every level is a link, including the last one. The last element carries aria-current="page": the screen reader announces "Air Max 90, link, current page". The user knows exactly where they are, without ambiguity.

What a screen reader announces:

navigation, Breadcrumb list, 4 items

  1. Home, link
  2. Shoes, link
  3. Trainers, link
  4. Air Max 90, link, current page

Separators

Between "Home" and "Shoes", you see a small chevron. It says "and then". It's a visual cue, nothing more. It carries no information that the ordered list doesn't already carry.

Yet most implementations stick the separator directly into the HTML:

breadcrumb-noisy.html
<!-- Screen reader announces:
     "Home, link, greater than, Shoes, link, greater than..." -->
<li><a href="/">Home</a> &gt;</li>
<li><a href="/shoes">Shoes</a> &gt;</li>

"Greater than". At every level. For someone using a screen reader, it's like someone repeating "and then" between every word in a sentence. You understand, but it's exhausting.

The solution: the separator is CSS, not HTML.

breadcrumb.css
nav[aria-label="Breadcrumb"] ol {
  display: flex;
  flex-wrap: wrap;
  list-style-type: "";
}
 
nav[aria-label="Breadcrumb"] li + li::before {
  content: "›" / "";
  margin-inline: var(--space-2);
}

A typographic character in CSS that doesn't factor into the accessible name of the links, automatically adapted to dark mode thanks to currentColor, and compatible with forced-colors with zero effort. Modern screen readers do read CSS generated content: it's the empty alternative text syntax, content: "›" / "", that makes the separator genuinely silent. No SVG, no image, no noise. And flex-wrap: wrap lets the breadcrumb wrap to the next line on small screens instead of overflowing.

A known side effect: with list-style: none, Safari removes the list semantics, and VoiceOver no longer announces "list, 4 items". The fix stays in CSS: declaring an empty marker with list-style-type: "" (or a zero-width space, "\200B") hides the bullets without touching the semantics. The alternative, role="list" on the <ol>, works too, but why reach for the scalpel when CSS is enough.

Schema.org — BreadcrumbList

The breadcrumb isn't just for humans. Search engines and LLMs use it to understand your site's structure, and to display it directly in search results.

You've probably seen it in Google: instead of a raw URL under the title, a readable path. "example.com › Shoes › Trainers". That's the schema.org breadcrumb in action.

Add a JSON-LD block to the page, alongside your <nav>:

breadcrumb-schema.html
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://example.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Shoes",
      "item": "https://example.com/shoes"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Trainers",
      "item": "https://example.com/shoes/trainers"
    },
    {
      "@type": "ListItem",
      "position": 4,
      "name": "Air Max 90",
      "item": "https://example.com/shoes/trainers/air-max"
    }
  ]
}
</script>

Three benefits:

  • SERP — Google displays the readable path instead of the raw URL
  • GEO — LLMs use structured data to place your page within a thematic hierarchy
  • Consistency — JSON-LD is a source of truth separate from the HTML, easy to generate server-side

To learn more about the impact of structured data on LLMs, see How GEO forces web accessibility.

Classic mistakes

<ul> instead of <ol>

Order matters. "Home" is level 1, "Shoes" is level 2. With <ul>, the screen reader announces a list, but without numbering, the hierarchy vanishes.

No <nav>

Without <nav>, the breadcrumb is an anonymous block of links. A screen reader can't identify it as navigation. The user can't jump straight to it. They have to scan the entire page to find it.

Missing aria-current

Without aria-current="page", nothing distinguishes the last link from the others. Visually, you can style it bold or grey. But to a screen reader, it's just another link in the list.

Missing aria-label

A site often has multiple <nav> elements, the main menu, the footer menu, the breadcrumb. Without aria-label, a screen reader announces "Navigation" three times with no distinction. With aria-label="Breadcrumb", it announces "navigation, Breadcrumb". The user knows where they are.

JavaScript-only breadcrumb

If your breadcrumb is rendered client-side by a framework, it doesn't exist until JavaScript has run. A crawler that doesn't execute it sees nothing. A browser where JS fails sees nothing. The breadcrumb is static content, it belongs in the initial HTML.

The stepper

What it is

The stepper looks like a breadcrumb. Same elements, same structure. But the intention is opposite.

The breadcrumb shows your position in a hierarchy, like a tree. You can jump to any level. The stepper shows your position in a process, like a straight line. You move forward, you go back, but you don't skip.

You encounter it in checkout tunnels, registration forms, setup wizards. Anywhere there are steps to follow in order.

stepper.html
<nav aria-label="Order progress">
  <ol>
    <li><a href="/cart">Cart</a></li>
    <li><a href="/shipping">Shipping</a></li>
    <li><a aria-current="step" href="/payment">Payment</a></li>
    <li>Confirmation</li>
  </ol>
</nav>

Three differences from the breadcrumb:

aria-current="step" instead of "page". The screen reader announces "Payment, link, current step". The user knows they're in a process, not a tree.

Future steps are not links. "Confirmation" is text in the <li>, not a link. The user hasn't reached that step yet. Offering a link would be misleading. Past steps are clickable (you can go back), the current step is identified, future steps are plain text.

No schema.org. The stepper is an interface component, not structured data. Google doesn't display it in the SERP. And that's expected, a checkout tunnel isn't meant to be indexed.

For separators, same technique as the breadcrumb: pure CSS, ::before with empty alternative text, zero noise for screen readers.

For the business logic of multi-step forms, validation, summary, error handling, see Beware of the Leopard, Part 3.

Classic stepper mistakes

The stepper is the most mistreated component on the web. Where the breadcrumb is sometimes absent, the stepper is often present but hollow.

Div soup

Most steppers in the wild look like this: a row of <div>s with .step-active, .step-completed, .step-disabled classes. Visually, it works. Circles, colours, checkmarks. Semantically, it's a void. A screen reader announces a sequence of text with no structure: no list, no order, no navigation. The user can't tell how many steps remain, or which one is current.

Invisible current step

Even with a correct <ol>, without aria-current="step", the current step is marked only by colour. A blue circle instead of grey. For someone who can't see colours, or can't see at all, every step looks the same.

Two questions are enough.

Is your structure a tree or a line?

A catalogue, documentation, an intranet: that's a tree. The user navigates a hierarchy and can jump to any level. → Breadcrumb.

A checkout tunnel, a registration form, a setup wizard: that's a line. The user moves forward step by step in an imposed order. → Stepper.

Can the user reach any point?

In a breadcrumb, yes. Every level is a link, always. In a stepper, no. Only completed steps are clickable. Future steps are locked.

If you're still unsure, ask yourself one last question: is the user exploring or progressing? The explorer needs a breadcrumb. The traveller needs a stepper.

The overall criteria

The breadcrumb directly addresses several WCAG success criteria:

  • 2.4.5 Multiple Ways (AA) — Provide more than one way to locate a page within a set of pages. An important nuance: the breadcrumb is only an advisory technique (G65) for this criterion, not a sufficient one, because it only gives access to ancestor pages. For knowing where you are, it's 2.4.8 below that it truly serves.
  • 2.4.8 Location (AAA) — Provide information about the user's location within the site. This criterion exists precisely for breadcrumbs — it's the textbook answer.
  • 3.2.3 Consistent Navigation (AA) — Navigation mechanisms that are repeated on multiple pages must occur in the same relative order. Your breadcrumb must appear in the same position on every page.
  • 4.1.2 Name, Role, Value (A) — All user interface components must have an accessible name and role. <nav> provides the role, aria-label provides the name, aria-current provides the state.

Sources