Accessible Navigation and Menus — Complete Guide
In this tutorial, you will learn about Accessible Navigation and Menus. We cover key concepts, practical examples, and best practices to help you master this topic.
Accessible navigation and menus use semantic HTML landmarks, clear aria-labels for disambiguation, keyboard support with arrow keys, aria-current for active states, and skip links to ensure all users can find and navigate content.
What You'll Learn
- Navigation landmarks and when to use multiple nav elements
- Keyboard patterns for menus (dropdown, mega menu, hamburger)
- aria-current for indicating current page or location
- Breadcrumb and pagination Accessibility
- Mobile navigation accessibility
Why It Matters
- Navigation is the primary way users find content
- Inaccessible navigation blocks access to the entire site
- Keyboard and screen reader users rely on consistent navigation patterns
- WCAG requires multiple ways to find content (Success Criterion 2.4.5)
Real-World Use
- An e-commerce site with categorized mega menu navigation
- A documentation site with sidebar navigation and breadcrumbs
- A news site with hamburger menu on mobile
- A government portal with skip links and multiple nav landmarks
flowchart LR A[Navigation] --> B[Landmarks] A --> C[Patterns] A --> D[States] B --> E[Primary, Secondary, Breadcrumb] C --> F[Dropdown, Mega Menu, Hamburger] D --> G[aria-current, aria-expanded]
Navigation Landmarks
Every page should have a navigation landmark that contains the primary navigation links. Additional navigation landmarks (breadcrumbs, table of contents, pagination) need distinct aria-labels.
Multiple Navigation Elements
When a page has multiple <nav> elements, each needs a unique aria-label so screen reader users can distinguish them:
<nav aria-label="Main">
<!-- Primary navigation -->
</nav>
<nav aria-label="Breadcrumb">
<!-- Breadcrumb links -->
</nav>
<nav aria-label="Table of Contents">
<!-- Page-internal links -->
</nav>
<nav aria-label="Pagination">
<!-- Page number links -->
</nav>
Code Example: Main Navigation with Dropdown
<nav aria-label="Main navigation">
<ul style="display:flex; gap:1rem; list-style:none; padding:0;">
<li><a href="/" aria-current="page">Home</a></li>
<li>
<button aria-expanded="false"
aria-controls="products-menu"
aria-haspopup="true"
id="products-btn"
onclick="toggleDropdown('products-menu', 'products-btn')">
Products
</button>
<ul id="products-menu"
role="menu"
aria-labelledby="products-btn"
hidden
style="position:absolute; list-style:none; background:white; border:1px solid #ddd; padding:0.5rem;">
<li role="none">
<a role="menuitem" href="/products/laptops">Laptops</a>
</li>
<li role="none">
<a role="menuitem" href="/products/tablets">Tablets</a>
</li>
<li role="none">
<a role="menuitem" href="/products/accessories">Accessories</a>
</li>
</ul>
</li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<script>
function toggleDropdown(menuId, buttonId) {
const menu = document.getElementById(menuId);
const button = document.getElementById(buttonId);
const isExpanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', !isExpanded);
menu.hidden = isExpanded;
if (!isExpanded) {
menu.querySelector('a').focus();
}
// Close on Escape
menu.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
menu.hidden = true;
button.setAttribute('aria-expanded', 'false');
button.focus();
}
});
}
</script>
Expected output: Screen readers announce "Main navigation, list, 4 items" and navigate by links. The Products button announces "Products, menu popup, collapsed" and changes to "expanded" when clicked. Arrow key navigation within the dropdown is supported.
Code Example: Breadcrumb Navigation
<nav aria-label="Breadcrumb">
<ol style="list-style:none; display:flex; gap:0.5rem; padding:0;">
<li>
<a href="/">Home</a>
<span aria-hidden="true">/</span>
</li>
<li>
<a href="/products">Products</a>
<span aria-hidden="true">/</span>
</li>
<li>
<a href="/products/laptops">Laptops</a>
<span aria-hidden="true">/</span>
</li>
<li aria-current="page">
UltraBook Pro 16
</li>
</ol>
</nav>
Expected output: Screen readers announce "Breadcrumb, 4 items" and navigate through each breadcrumb link. The current page item is announced as "UltraBook Pro 16, current page." The separators are hidden from screen readers.
Code Example: Pagination
<nav aria-label="Pagination for search results">
<ul style="display:flex; gap:0.25rem; list-style:none; padding:0;">
<li>
<a href="?page=1" aria-label="Go to first page">
First
</a>
</li>
<li>
<a href="?page=4" aria-label="Go to previous page, page 4">
Previous
</a>
</li>
<li>
<a href="?page=1" aria-label="Go to page 1">1</a>
</li>
<li>
<a href="?page=2" aria-label="Go to page 2">2</a>
</li>
<li>
<a href="?page=3" aria-label="Go to page 3">3</a>
</li>
<li>
<span aria-current="page" aria-label="Page 4, current page">4</span>
</li>
<li>
<a href="?page=5" aria-label="Go to page 5">5</a>
</li>
<li>
<a href="?page=5" aria-label="Go to next page, page 5">
Next
</a>
</li>
<li>
<a href="?page=10" aria-label="Go to last page, page 10">
Last
</a>
</li>
</ul>
</nav>
Expected output: Screen readers announce "Pagination for search results, 10 items." Each link has a descriptive aria-label that provides context. The current page is indicated with aria-current and does not have a link.
Mobile Navigation Accessibility
Mobile navigation often uses a hamburger icon that hides and shows the menu. This pattern must be accessible:
<header>
<nav aria-label="Main navigation">
<button aria-expanded="false"
aria-controls="mobile-menu"
id="menu-toggle"
onclick="toggleMobileMenu()">
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<path d="M3 6h18M3 12h18M3 18h18" stroke="currentColor" stroke-width="2"/>
</svg>
<span class="sr-only">Menu</span>
</button>
<ul id="mobile-menu"
role="menu"
aria-labelledby="menu-toggle"
hidden>
<li role="none"><a role="menuitem" href="/">Home</a></li>
<li role="none"><a role="menuitem" href="/products">Products</a></li>
<li role="none"><a role="menuitem" href="/about">About</a></li>
<li role="none"><a role="menuitem" href="/contact">Contact</a></li>
</ul>
</nav>
</header>
Expected output: The menu toggle button announces "Menu, button, collapsed." Activating it expands the menu and moves focus to the first link. The menu is keyboard navigable with Tab and Arrow keys.
Common Mistakes
- Not distinguishing multiple nav landmarks — Multiple
<nav>elements without distinct aria-labels confuse screen reader users who navigate by landmarks. - Dropdown menus that only work on hover — Dropdowns must also open on keyboard focus (or on click/Enter). Hover-only menus are inaccessible.
- Missing aria-current on current page — Without aria-current, screen reader users do not know which page they are on when navigating through links.
- Hamburger menu with no accessible label — The hamburger icon button needs an aria-label like "Menu" or "Open navigation."
- Focus not managed when menu opens/closes — When a menu opens, focus should move to the first item. When it closes, focus should return to the toggle button.
- Breadcrumb separators not hidden — Visual separators like
/or>should be hidden with aria-hidden="true" so screen readers do not announce them. - Pagination without descriptive labels — "Previous" and "Next" without page numbers provide insufficient context for screen reader users.
Practice Questions
- How do you distinguish multiple nav landmarks on a page? Use different aria-label values like "Main navigation", "Breadcrumb", and "Pagination."
- What attribute indicates the current page in a navigation? aria-current="page" on the link or element representing the current page.
- What keyboard keys should navigate menu items within a dropdown? Arrow keys (Up and Down) navigate between items. Enter or Space activates an item. Escape closes the menu.
- Why should breadcrumb separators be hidden from screen readers? Separators like "/" or ">" are visual only and add noise when announced by screen readers. Use aria-hidden="true".
- Challenge: Build a complete responsive navigation system with: a primary horizontal nav with dropdown submenus (2 levels deep), a breadcrumb trail, and a pagination component. All three must be fully keyboard accessible with proper ARIA. Test with a screen reader.
FAQ
{{< faq "Should I use role=\"navigation\" on nav elements?" "No. The nav element already has an implicit role of navigation. Adding role=\"navigation\" is redundant." >}} {{< faq "What is the best way to indicate a selected tab in tab navigation?" "Use aria-selected=\"true\" on the selected tab, aria-selected=\"false\" on others, and aria-current=\"page\" on the currently viewed page link." >}}Mini Project
Build a complete accessible e-commerce navigation system. Include: a top bar with secondary nav (sign in, cart, wishlist), a primary navigation with 3 dropdown menus (each with 3-5 items), a breadcrumb trail on the product page, a sidebar category filter with checkboxes, and a pagination component at the bottom of search results. Implement keyboard navigation for all components, proper ARIA landmarks, aria-current for active states, and focus management. Test with keyboard only and a screen reader. Document the navigation flow.
What's Next
Continue with Lesson 18: Accessible Modals and Dialogs to learn how to create modal dialogs that are fully accessible.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro