Accessible Navigation Patterns: Menus, Breadcrumbs, and Skip Links
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.
Skip 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>Dropdown Menus
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();
}
});
}Breadcrumbs
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:
- Keyboard Accessibility Guide: Complete WCAG Compliance
- Skip Links: Implementation Guide for WCAG 2.4.1
- Mobile Accessibility: Touch and Screen Reader Patterns
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.


Automate the software work for accessibility compliance, end-to-end.
Empowering businesses with seamless digital accessibility solutions—simple, inclusive, effective.
Book a Demo