Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Implement accessible breadcrumb navigation

Breadcrumb navigation is implemented with proper semantic markup and ARIA attributes for accessibility.

Utilities
Quick take
Typical fix time 15 min
  • Use nav element with aria-label='Breadcrumb'
  • Use ordered list (ol) for semantic structure
  • Mark current page with aria-current='page'
  • Add structured data for SEO benefits
Why it matters: Proper breadcrumb markup helps screen reader users understand site hierarchy and their current location, while also improving SEO through structured data.

Rule Details

Accessible breadcrumbs help users understand their location within the site hierarchy.

Code Example

<nav aria-label="Breadcrumb" class="breadcrumb">
  <ol>
    <li>
      <a href="/">Home</a>
    </li>
    <li>
      <a href="/products">Products</a>
    </li>
    <li>
      <a href="/products/electronics">Electronics</a>
    </li>
    <li>
      <a href="/products/electronics/laptops" aria-current="page">Laptops</a>
    </li>
  </ol>
</nav>

Why It Matters

Proper breadcrumb markup helps screen reader users understand site hierarchy and their current location, while also improving SEO through structured data.

Accessibility Requirements

RequirementImplementation
Container<nav> with aria-label="Breadcrumb"
List structureOrdered list (<ol>)
Current pagearia-current="page"
SeparatorsCSS or aria-hidden text

React Breadcrumb Component

import { useId } from 'react'
 
interface BreadcrumbItem {
  label: string
  href: string
}
 
interface BreadcrumbProps {
  items: BreadcrumbItem[]
}
 
export function Breadcrumb({ items }: BreadcrumbProps) {
  if (items.length === 0) return null
 
  return (
    <nav aria-label="Breadcrumb" className="breadcrumb">
      <ol className="breadcrumb__list">
        {items.map((item, index) => {
          const isLast = index === items.length - 1
 
          return (
            <li key={item.href} className="breadcrumb__item">
              {isLast ? (
                <span aria-current="page" className="breadcrumb__current">
                  {item.label}
                </span>
              ) : (
                <a href={item.href} className="breadcrumb__link">
                  {item.label}
                </a>
              )}
            </li>
          )
        })}
      </ol>
    </nav>
  )
}

Usage

<Breadcrumb
  items={[
    { label: 'Home', href: '/' },
    { label: 'Products', href: '/products' },
    { label: 'Electronics', href: '/products/electronics' },
    { label: 'Laptops', href: '/products/electronics/laptops' },
  ]}
/>

With Structured Data (JSON-LD)

interface BreadcrumbItem {
  label: string
  href: string
}
 
interface BreadcrumbProps {
  items: BreadcrumbItem[]
  baseUrl?: string
}
 
export function BreadcrumbWithSchema({ items, baseUrl = '' }: BreadcrumbProps) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'BreadcrumbList',
    itemListElement: items.map((item, index) => ({
      '@type': 'ListItem',
      position: index + 1,
      name: item.label,
      item: `${baseUrl}${item.href}`,
    })),
  }
 
  return (
    <>
      <nav aria-label="Breadcrumb" className="breadcrumb">
        <ol className="breadcrumb__list">
          {items.map((item, index) => {
            const isLast = index === items.length - 1
 
            return (
              <li key={item.href} className="breadcrumb__item">
                {isLast ? (
                  <span aria-current="page">{item.label}</span>
                ) : (
                  <a href={item.href}>{item.label}</a>
                )}
              </li>
            )
          })}
        </ol>
      </nav>
 
      {/* Structured data for SEO */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
      />
    </>
  )
}

Next.js Dynamic Breadcrumbs

'use client'
 
import { usePathname } from 'next/navigation'
 
interface PathConfig {
  [key: string]: string
}
 
const pathLabels: PathConfig = {
  products: 'Products',
  electronics: 'Electronics',
  laptops: 'Laptops',
  accessories: 'Accessories',
}
 
export function AutoBreadcrumb() {
  const pathname = usePathname()
  const segments = pathname.split('/').filter(Boolean)
 
  const items = segments.map((segment, index) => ({
    label: pathLabels[segment] || segment.replace(/-/g, ' '),
    href: '/' + segments.slice(0, index + 1).join('/'),
  }))
 
  // Add home at the beginning
  items.unshift({ label: 'Home', href: '/' })
 
  return <Breadcrumb items={items} />
}

With Icons

interface BreadcrumbProps {
  items: BreadcrumbItem[]
  homeIcon?: React.ReactNode
  separator?: React.ReactNode
}
 
export function Breadcrumb({
  items,
  homeIcon = '🏠',
  separator = '/'
}: BreadcrumbProps) {
  return (
    <nav aria-label="Breadcrumb" className="breadcrumb">
      <ol className="breadcrumb__list">
        {items.map((item, index) => {
          const isFirst = index === 0
          const isLast = index === items.length - 1
 
          return (
            <li key={item.href} className="breadcrumb__item">
              {index > 0 && (
                <span aria-hidden="true" className="breadcrumb__separator">
                  {separator}
                </span>
              )}
 
              {isLast ? (
                <span aria-current="page" className="breadcrumb__current">
                  {item.label}
                </span>
              ) : (
                <a href={item.href} className="breadcrumb__link">
                  {isFirst && homeIcon && (
                    <span aria-hidden="true" className="breadcrumb__icon">
                      {homeIcon}
                    </span>
                  )}
                  {isFirst && homeIcon ? (
                    <span className="sr-only">{item.label}</span>
                  ) : (
                    item.label
                  )}
                </a>
              )}
            </li>
          )
        })}
      </ol>
    </nav>
  )
}

Styling

.breadcrumb__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.875rem;
}
 
.breadcrumb__item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
 
/* CSS-based separators (alternative to inline) */
.breadcrumb__item:not(:first-child)::before {
  content: '/';
  color: #999;
}
 
.breadcrumb__link {
  color: #0066cc;
  text-decoration: none;
}
 
.breadcrumb__link:hover {
  text-decoration: underline;
}
 
.breadcrumb__link:focus-visible {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
  border-radius: 2px;
}
 
.breadcrumb__current {
  color: #666;
  font-weight: 500;
}
 
.breadcrumb__separator {
  color: #999;
}
 
.breadcrumb__icon {
  display: inline-flex;
  margin-right: 0.25rem;
}
 
/* Screen reader only */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}
 
/* Responsive: hide middle items on small screens */
@media (max-width: 640px) {
  .breadcrumb__list {
    flex-wrap: nowrap;
    overflow: hidden;
  }
 
  /* Show first, last, and ellipsis */
  .breadcrumb__item:not(:first-child):not(:last-child) {
    display: none;
  }
 
  .breadcrumb__item:first-child + .breadcrumb__item:not(:last-child)::before {
    content: '...';
  }
}

Common Patterns

<!-- Don't link the current page -->
<nav aria-label="Breadcrumb">
  <ol>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><span aria-current="page">Laptops</span></li>
  </ol>
</nav>
 
<!-- With separators hidden from screen readers -->
<nav aria-label="Breadcrumb">
  <ol>
    <li><a href="/">Home</a></li>
    <li aria-hidden="true">/</li>
    <li><a href="/products">Products</a></li>
    <li aria-hidden="true">/</li>
    <li><span aria-current="page">Laptops</span></li>
  </ol>
</nav>

Verification

  1. Navigate with screen reader (announces "Breadcrumb navigation")
  2. Verify ordered list structure
  3. Confirm current page is announced
  4. Check all links are keyboard focusable
  5. Verify separators aren't announced
  6. Test structured data with Rich Results Test
  7. Check responsive behavior on small screens
Don't Link the Current Page

The current page in breadcrumbs should not be a link—it should be plain text with aria-current="page". Linking to the current page creates confusion.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Verify breadcrumbs use nav element with aria-label, ordered list markup, and aria-current for the current page.

Fix

Auto-fix issues

Implement breadcrumbs using semantic nav, ol/li structure, and proper ARIA attributes including aria-current='page'.

Explain

Learn more

Explain how properly marked up breadcrumbs improve navigation, SEO, and screen reader accessibility.

Review

Code review

Review templates, server-rendered HTML, and shared components that output markup related to Implement accessible breadcrumb navigation. Flag exact elements, attributes, and routes where the rendered HTML violates the rule.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

Nu Html Checker
validator.w3.orgTool
Breadcrumb Pattern – UX Patterns for Developers

Comprehensive UX pattern guide covering anatomy, accessibility, best practices, and implementation.

uxpatterns.devGuide

Rules that often go hand-in-hand with this one.

Make pagination accessible

Pagination controls are accessible with proper ARIA labels, keyboard navigation, and current page indication.

HTML
Make accordions keyboard navigable

Accordion components use proper ARIA attributes and keyboard interactions for screen reader accessibility.

Accessibility
External Link Security

Links that open in a new tab using target='_blank' must include rel='noopener noreferrer' to prevent the opened page from accessing the opener's window context.

Security
Make carousels accessible

Carousels and sliders are accessible with pause controls, keyboard navigation, and proper ARIA attributes.

Accessibility

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385