Blog

Accessible Navigation Patterns: Menus, Breadcrumbs, and Skip Links

TestParty
TestParty
July 27, 2025

Accessible navigation enables users to efficiently find and reach content. For screen reader users, navigation is how they understand site structure and locate pages. For keyboard users, navigation must be fully operable without a mouse. Poor navigation accessibility—missing skip links, keyboard-trapped menus, confusing breadcrumbs—creates barriers across every page.

WCAG requires multiple navigation mechanisms (2.4.5), consistent navigation (3.2.3), and navigable structure through headings and landmarks (2.4.1, 2.4.6). This guide covers implementing accessible navigation patterns that meet these requirements.

Q: What makes navigation WCAG compliant?

A: WCAG-compliant navigation includes: skip links to bypass repetitive content, keyboard-operable menus, proper landmark regions, consistent structure across pages, descriptive link text, and visible focus indicators. Screen reader users should understand where navigation leads without activating links.

Why Skip Links Matter

Skip links let keyboard and screen reader users bypass repetitive navigation to reach main content directly. Without skip links, users must tab through dozens of navigation links on every page before reaching content.

Basic Skip Link Implementation

<body>
  <!-- Skip link: first focusable element -->
  <a href="#main-content" class="skip-link">
    Skip to main content
  </a>

  <header>
    <nav><!-- Navigation links --></nav>
  </header>

  <main id="main-content" tabindex="-1">
    <h1>Page Title</h1>
    <!-- Page content -->
  </main>
</body>

Skip Link Styling

Skip links should be visible when focused:

.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #000;
  color: #fff;
  padding: 8px 16px;
  z-index: 100;
  text-decoration: none;
}

.skip-link:focus {
  top: 0;
}

Multiple Skip Links

For complex pages, offer multiple skip targets:

<nav aria-label="Skip links" class="skip-links">
  <a href="#main-content">Skip to main content</a>
  <a href="#primary-nav">Skip to navigation</a>
  <a href="#search">Skip to search</a>
</nav>

Target Element Requirements

The skip link target needs tabindex="-1" to receive focus in all browsers:

<main id="main-content" tabindex="-1">

Without tabindex="-1", some browsers don't move focus to the target, making the skip link ineffective.

Primary Navigation

Semantic Navigation Structure

Use <nav> element with descriptive label:

<nav aria-label="Main">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Current Page Indication

Indicate current page with aria-current:

<nav aria-label="Main">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products" aria-current="page">Products</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Style the current page visually:

nav a[aria-current="page"] {
  font-weight: bold;
  border-bottom: 2px solid currentColor;
}

Multiple Navigation Regions

When a page has multiple <nav> elements, distinguish them with labels:

<nav aria-label="Main">
  <!-- Primary navigation -->
</nav>

<nav aria-label="Product categories">
  <!-- Category navigation -->
</nav>

<nav aria-label="Footer">
  <!-- Footer links -->
</nav>

Disclosure Pattern (Recommended)

The disclosure pattern uses buttons to toggle submenus:

<nav aria-label="Main">
  <ul class="menu">
    <li>
      <a href="/">Home</a>
    </li>
    <li class="has-submenu">
      <button aria-expanded="false"
              aria-controls="products-menu">
        Products
        <span aria-hidden="true">â–Ľ</span>
      </button>
      <ul id="products-menu" class="submenu" hidden>
        <li><a href="/products/shoes">Shoes</a></li>
        <li><a href="/products/clothing">Clothing</a></li>
        <li><a href="/products/accessories">Accessories</a></li>
      </ul>
    </li>
    <li>
      <a href="/about">About</a>
    </li>
  </ul>
</nav>

<script>
document.querySelectorAll('.has-submenu button').forEach(button => {
  button.addEventListener('click', () => {
    const expanded = button.getAttribute('aria-expanded') === 'true';
    button.setAttribute('aria-expanded', !expanded);

    const submenu = document.getElementById(
      button.getAttribute('aria-controls')
    );
    submenu.hidden = expanded;
  });
});
</script>

Keyboard Interaction

Dropdown menus should support:

  • Enter/Space: Toggle submenu
  • Escape: Close submenu, return focus to trigger
  • Tab: Move through menu items normally
  • Arrow keys (optional): Navigate within menu
function handleMenuKeydown(event, button, submenu) {
  switch (event.key) {
    case 'Escape':
      closeSubmenu(button, submenu);
      button.focus();
      break;
    case 'ArrowDown':
      if (button.getAttribute('aria-expanded') === 'true') {
        event.preventDefault();
        submenu.querySelector('a').focus();
      }
      break;
  }
}

Hover vs Click Activation

Recommendation: Use click/Enter to activate submenus, not hover alone.

Problems with hover-only:

  • Doesn't work for keyboard users
  • Touch devices don't have hover
  • Accidental activation frustrates users

If using hover, also support click:

/* CSS hover activation */
.has-submenu:hover .submenu,
.has-submenu:focus-within .submenu {
  display: block;
}
// JavaScript click backup
button.addEventListener('click', toggleSubmenu);

Mega Menus

Large mega menus need careful structure:

<nav aria-label="Main">
  <ul class="menu">
    <li class="mega-menu-item">
      <button aria-expanded="false"
              aria-controls="products-mega">
        Products
      </button>
      <div id="products-mega" class="mega-menu" hidden>
        <div class="mega-menu-section">
          <h3>Footwear</h3>
          <ul>
            <li><a href="/shoes/running">Running</a></li>
            <li><a href="/shoes/casual">Casual</a></li>
            <li><a href="/shoes/dress">Dress</a></li>
          </ul>
        </div>
        <div class="mega-menu-section">
          <h3>Clothing</h3>
          <ul>
            <li><a href="/clothing/shirts">Shirts</a></li>
            <li><a href="/clothing/pants">Pants</a></li>
            <li><a href="/clothing/outerwear">Outerwear</a></li>
          </ul>
        </div>
      </div>
    </li>
  </ul>
</nav>

Mobile Navigation

Mobile Menu Toggle

<button class="menu-toggle"
        aria-expanded="false"
        aria-controls="mobile-menu"
        aria-label="Menu">
  <span class="hamburger-icon" aria-hidden="true"></span>
</button>

<nav id="mobile-menu" class="mobile-nav" hidden>
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Mobile Menu Behavior

const menuToggle = document.querySelector('.menu-toggle');
const mobileMenu = document.getElementById('mobile-menu');

menuToggle.addEventListener('click', () => {
  const expanded = menuToggle.getAttribute('aria-expanded') === 'true';

  menuToggle.setAttribute('aria-expanded', !expanded);
  mobileMenu.hidden = expanded;

  if (!expanded) {
    // Focus first link when opening
    mobileMenu.querySelector('a').focus();
  }
});

// Close on Escape
mobileMenu.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    menuToggle.setAttribute('aria-expanded', 'false');
    mobileMenu.hidden = true;
    menuToggle.focus();
  }
});

Focus Trapping for Overlays

If mobile menu overlays content, trap focus:

function trapFocus(container) {
  const focusableElements = container.querySelectorAll(
    'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  const first = focusableElements[0];
  const last = focusableElements[focusableElements.length - 1];

  container.addEventListener('keydown', (e) => {
    if (e.key !== 'Tab') return;

    if (e.shiftKey && document.activeElement === first) {
      e.preventDefault();
      last.focus();
    } else if (!e.shiftKey && document.activeElement === last) {
      e.preventDefault();
      first.focus();
    }
  });
}

Breadcrumb Structure

<nav aria-label="Breadcrumb">
  <ol class="breadcrumb">
    <li>
      <a href="/">Home</a>
    </li>
    <li>
      <a href="/products">Products</a>
    </li>
    <li>
      <a href="/products/shoes">Shoes</a>
    </li>
    <li>
      <a href="/products/shoes/running" aria-current="page">
        Running Shoes
      </a>
    </li>
  </ol>
</nav>

Visual Separators

Use CSS for separators, not content:

.breadcrumb li:not(:last-child)::after {
  content: "/";
  margin: 0 0.5rem;
  color: #666;
}

/* Or use aria-hidden decorative element */
<!-- Alternatively with explicit separator -->
<li>
  <a href="/">Home</a>
  <span aria-hidden="true">/</span>
</li>

Current Page in Breadcrumbs

The current page should:

  • Use aria-current="page"
  • Optionally be non-linked (just text)
  • Be visually distinct
<!-- Option 1: Linked with aria-current -->
<li>
  <a href="/products/shoes/running" aria-current="page">
    Running Shoes
  </a>
</li>

<!-- Option 2: Text only for current page -->
<li>
  <span aria-current="page">Running Shoes</span>
</li>

Pagination

Accessible Pagination

<nav aria-label="Pagination">
  <ul class="pagination">
    <li>
      <a href="/products?page=1"
         aria-label="Go to first page">
        First
      </a>
    </li>
    <li>
      <a href="/products?page=2"
         aria-label="Go to previous page">
        Previous
      </a>
    </li>
    <li>
      <a href="/products?page=1"
         aria-label="Go to page 1">
        1
      </a>
    </li>
    <li>
      <a href="/products?page=2"
         aria-label="Go to page 2">
        2
      </a>
    </li>
    <li>
      <a href="/products?page=3"
         aria-current="page"
         aria-label="Page 3, current page">
        3
      </a>
    </li>
    <li>
      <a href="/products?page=4"
         aria-label="Go to page 4">
        4
      </a>
    </li>
    <li>
      <a href="/products?page=4"
         aria-label="Go to next page">
        Next
      </a>
    </li>
    <li>
      <a href="/products?page=10"
         aria-label="Go to last page">
        Last
      </a>
    </li>
  </ul>
</nav>

Page Numbers for Screen Readers

Without aria-label, screen readers announce just "3" for pagination links—insufficient context:

<!-- Without aria-label: "link, 3" -->
<a href="/products?page=3">3</a>

<!-- With aria-label: "link, Go to page 3" -->
<a href="/products?page=3" aria-label="Go to page 3">3</a>

Search Navigation

Accessible Search Form

<form role="search" action="/search" method="get">
  <label for="site-search" class="visually-hidden">
    Search products
  </label>
  <input type="search"
         id="site-search"
         name="q"
         placeholder="Search..."
         aria-label="Search products">
  <button type="submit">
    <span class="visually-hidden">Submit search</span>
    <svg aria-hidden="true"><!-- Search icon --></svg>
  </button>
</form>

Search Autocomplete

<div class="search-wrapper">
  <label for="search">Search</label>
  <input type="search"
         id="search"
         role="combobox"
         aria-expanded="false"
         aria-controls="search-results"
         aria-autocomplete="list">

  <ul id="search-results"
      role="listbox"
      hidden>
    <!-- Populated dynamically -->
  </ul>
</div>

Testing Navigation Accessibility

Automated Testing

TestParty identifies navigation issues:

  • Missing skip links
  • Navigation without landmark
  • Missing current page indication
  • Focus indicator problems
  • Keyboard trap detection

Manual Testing Checklist

Skip links:

  • [ ] Skip link is first focusable element
  • [ ] Skip link visible when focused
  • [ ] Skip link moves focus to main content
  • [ ] Focus is visible at target

Dropdown menus:

  • [ ] Submenus activate with Enter/Space
  • [ ] Escape closes submenus
  • [ ] Focus moves logically through menu
  • [ ] Expanded state announced to screen readers

Mobile navigation:

  • [ ] Menu toggle has accessible name
  • [ ] Menu opens/closes with keyboard
  • [ ] Focus managed appropriately
  • [ ] Escape closes menu

Breadcrumbs:

  • [ ] Has breadcrumb landmark or label
  • [ ] Current page indicated
  • [ ] Links are descriptive

FAQ Section

Q: Should navigation links open in new tabs?

A: Generally no—unexpected new tabs disorient users. If you must open new tabs, warn users: "Opens in new tab" in link text or aria-label.

Q: How do I handle very long navigation?

A: Use skip links to bypass, organize with submenus, consider secondary navigation patterns. Don't hide critical navigation—make it scannable.

Q: Do I need both hamburger icon and "Menu" text?

A: The hamburger icon alone isn't universally understood. Include visible "Menu" text or at minimum provide aria-label="Menu" for screen readers.

Q: Should breadcrumbs use `role="navigation"`?

A: The <nav> element provides navigation role implicitly. Add aria-label="Breadcrumb" to distinguish from main navigation.

Q: How many navigation landmarks is too many?

A: There's no strict limit, but each should be labeled distinctively. Users scanning landmarks shouldn't encounter five "navigation" regions—label each: "Main," "Footer," "Breadcrumb," etc.

Key Takeaways

  • Skip links are essential. First focusable element should skip to main content.
  • Use `<nav>` with labels. Distinguish multiple navigation regions with aria-label.
  • Indicate current page with aria-current="page" and visual styling.
  • Dropdown menus need keyboard support. Click/Enter to open, Escape to close, Tab to navigate.
  • Mobile menus need focus management. Move focus into menu when opening, return focus when closing.
  • Test with keyboard and screen reader. Navigation issues are immediately apparent with real testing.

Conclusion

Navigation accessibility impacts every page on your site. Users who can't efficiently navigate can't use your site at all—making accessible navigation patterns a fundamental requirement, not an enhancement.

The patterns covered here—skip links, dropdown menus, breadcrumbs, mobile navigation—follow established accessibility best practices. Implementing them correctly ensures keyboard users and screen reader users can navigate as efficiently as mouse users.

TestParty identifies navigation accessibility issues and provides specific fixes. For e-commerce sites, this means accessible product category navigation, site search, and checkout flows.

Ready to fix your navigation accessibility? Get a free accessibility scan to identify navigation issues across your site.


Related Articles:


Written with AI assistance and reviewed by our accessibility team. Not legal advice—consult professionals for your specific compliance needs.

Stay informed

Accessibility insights delivered
straight to your inbox.

Contact Us

Automate the software work for accessibility compliance, end-to-end.

Empowering businesses with seamless digital accessibility solutions—simple, inclusive, effective.

Book a Demo