// pattern
Accessible Breadcrumb Pattern
A breadcrumb navigation that uses native <nav> with aria-label, an ordered list for hierarchy, aria-current="page" on the current page, and CSS-only separators. Zero JavaScript required.
// 01 · live demo
Live Demo
Use Tab to move between breadcrumb links. Press Enter to follow a link.
Inaccessible version (div-based):
No <nav>, no list structure, no aria-current, and the > separator text is announced by screen readers. Not keyboard-navigable.
// 02 · the code
The Code
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/products/">Products</a></li>
<li><a href="/products/electronics/">Electronics</a></li>
<li><span aria-current="page">Headphones</span></li>
</ol>
</nav>
nav[aria-label="Breadcrumb"] ol {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.25rem;
}
nav[aria-label="Breadcrumb"] li {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.875rem;
}
/* CSS separator — decorative, not announced */
nav[aria-label="Breadcrumb"] li:not(:last-child)::after {
content: "/";
color: #6b7280;
pointer-events: none;
}
nav[aria-label="Breadcrumb"] a {
color: #5b2a86;
text-decoration: underline;
text-underline-offset: 2px;
padding: 0.25rem 0.125rem;
border-radius: 0.25rem;
/* SC 2.5.8: minimum target size */
min-height: 1.5rem;
display: inline-flex;
align-items: center;
}
nav[aria-label="Breadcrumb"] a:hover {
text-decoration-thickness: 2px;
}
/* SC 2.4.13: visible focus indicator */
nav[aria-label="Breadcrumb"] a:focus-visible {
outline: 2px solid #5b2a86;
outline-offset: 2px;
border-radius: 0.25rem;
}
/* Current page — not a link */
nav[aria-label="Breadcrumb"] [aria-current="page"] {
color: #1f2937;
font-weight: 600;
}
<nav> element, <ol> list, and aria-current="page" attribute handle all the semantics. CSS ::after pseudo-elements handle the visual separators. There is no interactive behavior that requires JavaScript.
// 03 · why these decisions
Why These Decisions
Why <nav> with aria-label="Breadcrumb"
The <nav> element creates a navigation landmark that screen readers list alongside other landmarks on the page. The aria-label="Breadcrumb" distinguishes this navigation from others (like the main navigation or footer navigation). Without the label, screen readers announce it as a generic "navigation" landmark, making it hard for users to tell which navigation region is which.
Why <ol> instead of <ul> or plain divs
An ordered list conveys hierarchy — the items have a meaningful sequence from the root page to the current page. Screen readers announce "list with 4 items" and each item's position ("1 of 4", "2 of 4"), giving users a clear sense of the navigation depth. An unordered list would still be better than divs, but an ordered list communicates that the order matters. Plain divs provide no list semantics at all.
Why aria-current="page" on the last item
The aria-current="page" attribute tells screen readers that this item represents the current page. Without it, screen readers have no programmatic way to distinguish the current page from the other breadcrumb items. Users would have to guess based on the fact that the last item isn't a link — but that's a visual cue that doesn't translate to assistive technology.
Why CSS separators instead of text characters
Using CSS ::after pseudo-elements for separators makes them purely decorative. Screen readers skip CSS-generated content in this context, so users don't hear "Home slash Products slash Electronics" between every breadcrumb item. If you use actual text characters like > or / in the HTML, screen readers announce them, creating a noisy and confusing experience.
Why the current page is a <span>, not a link
The current page should not link to itself. A self-referencing link is confusing — it appears interactive but reloads the same page, providing no value. Using a <span> removes the item from the tab order and signals that it is not a navigation target. Combined with aria-current="page", this clearly communicates to all users that they are already on this page.
// 04 · keyboard interaction
Keyboard Interaction
| Key | Action |
|---|---|
| Tab | Moves focus to the next breadcrumb link. The current page item (a <span>) is not in the tab order. |
| Shift + Tab | Moves focus to the previous breadcrumb link |
| Enter | Follows the focused breadcrumb link, navigating to that page |
<a> elements, all keyboard behavior comes for free. Links are focusable, tabbable, and activatable with Enter by default. There is no need for arrow key navigation, roving tabindex, or any JavaScript keyboard handlers.
// 05 · wcag 2.2 success criteria
WCAG 2.2 Success Criteria
This pattern satisfies the following WCAG 2.2 success criteria:
- 2.4.8 Location Level AAA — Breadcrumbs provide information about the user's location within the site hierarchy, helping users understand where they are and how to navigate back to higher-level pages.
-
1.3.1 Info and Relationships
Level A
— The
<nav>landmark,<ol>list structure, andaria-current="page"attribute programmatically convey the breadcrumb structure and the user's current location. - 2.4.4 Link Purpose (In Context) Level A — Each breadcrumb link has descriptive text that clearly identifies its destination page.
- 2.4.3 Focus Order Level A — Focus moves through breadcrumb links in the same left-to-right order as the visual presentation. The current page is not focusable, preventing confusion.
- 2.4.13 Focus Appearance Level AAA — Breadcrumb links have a visible 2px solid outline focus indicator with sufficient contrast.
- 2.5.8 Target Size (Minimum) Level AA — Breadcrumb links have padding and a minimum height to meet the 24x24px minimum target size requirement.
// 06 · screen reader behavior
Screen Reader Behavior
When navigating to the breadcrumb region
- NVDA: "Breadcrumb, navigation landmark" — announces the aria-label and the landmark role, then "list with 4 items" when entering the list
- JAWS: "Breadcrumb navigation region" — announces the landmark, then "list of 4 items, Home, link, 1 of 4"
- VoiceOver: "Breadcrumb, navigation" — announces the landmark label and role, then reads list items with their positions
When navigating through the links
- NVDA: "Home, link, 1 of 4" then "Products, link, 2 of 4" then "Electronics, link, 3 of 4" — announces each link text and position. CSS separators are not announced.
- JAWS: "Home, link" then "Products, link" then "Electronics, link" — announces link text and role
- VoiceOver: "Home, link, 1 of 4" then "Products, link, 2 of 4" — includes position information from the ordered list
When reaching the current page
- NVDA: "Headphones, current page, 4 of 4" — announces the text and the
aria-current="page"state - JAWS: "Headphones, current page" — clearly indicates this is the current page, not a link
- VoiceOver: "Headphones, current page, text, 4 of 4" — announces the current page state and position
Why CSS separators matter
Because the separators are rendered with CSS ::after pseudo-elements, screen readers do not announce them. If the separators were actual text characters (like >) in the HTML, screen readers would announce "Home, greater than, Products, greater than, Electronics" — a much noisier experience that adds no useful information.
// 07 · common mistakes
Common Mistakes
<div> elements instead of <nav> and <ol>
Without the <nav> landmark, screen readers cannot identify the breadcrumb as a navigation region. Without the <ol>, there is no list structure — screen readers cannot announce the number of items or each item's position. The combination of <nav> and <ol> gives assistive technology everything it needs to understand the breadcrumb structure.
aria-label on the <nav> element
Most pages have multiple <nav> elements (main navigation, footer navigation, breadcrumbs). Without an aria-label, screen readers announce them all as "navigation" with no way to distinguish them. Adding aria-label="Breadcrumb" lets users identify and jump to the breadcrumb navigation from any landmark list.
<span> with aria-current="page" instead. This removes the item from the tab order and clearly signals that it represents the user's current location, not a navigation target.
> in the HTML
Text characters placed between breadcrumb items are announced by screen readers. Users hear "Home, greater than, Products, greater than" instead of just "Home, Products." Use CSS ::after pseudo-elements for separators — they are decorative and not announced.
aria-current="page"
Without aria-current="page", screen readers have no programmatic way to identify which breadcrumb item represents the current page. Sighted users can see the visual difference (no underline, bold text), but this information must also be conveyed to assistive technology.
// 08 · native html vs. aria
Native HTML vs. ARIA
Breadcrumbs are one of the patterns that mostly use native HTML, needing only minimal ARIA. This table shows what each approach contributes.
| Feature | Native HTML | ARIA |
|---|---|---|
| Navigation landmark | <nav> — creates a navigation landmark automatically |
role="navigation" — equivalent, but unnecessary when using <nav> |
| Landmark label | None — <nav> alone doesn't provide a distinguishing label |
aria-label="Breadcrumb" — required to identify the navigation purpose |
| List structure | <ol> — ordered list conveys hierarchy, item count, and positions |
Not needed — native list semantics handle this entirely |
| Link semantics | <a> — focusable, keyboard-activatable, announces as "link" |
Not needed — native links provide full accessibility |
| Current page indicator | None — HTML has no native "current page" attribute | aria-current="page" — required to identify the current location |
| Separators | CSS ::after — decorative, not announced by screen readers |
Not needed — CSS handles visual separators without ARIA |
| Keyboard navigation | Native <a> elements — tabbable and activatable by default |
Not needed — no custom keyboard handling required |
| Focus management | Native focus order follows DOM order, which matches visual order | Not needed — no focus management required |
<nav> element, <ol> list, and <a> links provide the vast majority of the accessibility. The only ARIA needed is aria-label to name the landmark and aria-current="page" to identify the current page — two simple attributes.