Accessible Navigation & Menus
Navigation is how users move through your site — and if it’s not accessible, users with disabilities can’t find content, complete tasks, or understand your site’s structure. Accessible navigation means semantic landmarks, clear link text, keybord-operable menus, and persistent wayfinding cues.
What You’ll Learn
By the end of this tutorial, you’ll understand HTML5 landmark elements, skip links, accessible breadcrumbs, responsive hamburger menus with proper ARIA, aria-current for active states, keyboard-operable mega menus, search accessibility, accessible pagination, and site map best practices.
Why Accessible Navigation Matters
Navigation is the first thing screen reader users encounter on every page. If it’s not structured with landmarks and clear link text, they can’t skip to content or understand where they are. According to the WebAIM Million survey, “empty links” and “missing form labels” are the most common accessibility failures, and navigation components are frequent offenders. At DodaTech, Doda Browser includes a navigation landmark viewer that shows the page structure hierarchy.
Accessible Navigation Learning Path
flowchart LR
A[Accessibility Overview] --> B[WCAG Compliance]
B --> C[Keyboard Navigation]
C --> D[Accessible Navigation]
D:::current
classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
Landmark Elements
HTML5 landmark elements help screen reader users navigate the page structure:
<header role="banner">
<!-- Site branding, logo, global navigation -->
<a href="/" aria-label="DodaTech home">
<img src="logo.svg" alt="">
</a>
</header>
<nav aria-label="Main navigation">
<!-- Primary navigation links -->
</nav>
<main>
<h1>Page title</h1>
<!-- Primary page content -->
</main>
<aside aria-label="Related articles">
<!-- Complementary content -->
</aside>
<footer role="contentinfo">
<!-- Copyright, legal links, contact info -->
</footer>Why explicit role attributes on landmarks? While HTML5 elements have implicit roles, adding explicit roles ensures older browsers and screen readers recognize them. The W3C recommends this approach for broad compatibility.
Multiple Landmarks of the Same Type
When you have multiple <nav> or <section> elements, label them with aria-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>
</ul>
</nav>
<nav aria-label="Footer">
<ul>
<li><a href="/privacy">Privacy Policy</a></li>
<li><a href="/terms">Terms of Service</a></li>
</ul>
</nav>Screen reader navigation: Users can press D (NVDA) or use the Rotor (VoiceOver) to jump between landmarks. Without labels, both <nav> elements show as “Navigation” — the user can’t distinguish them.
Skip Links
Skip links let keyboard users bypass navigation and jump to main content. They should be the first focusable element on the page:
<style>
.skip-link {
position: absolute;
top: -100%;
left: 8px;
padding: 12px 24px;
background: #005fcc;
color: white;
z-index: 10000;
border-radius: 0 0 4px 4px;
text-decoration: none;
font-weight: 600;
}
.skip-link:focus {
top: 0;
}
</style>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header><!-- nav, logo, etc. --></header>
<main id="main-content" tabindex="-1">
<h1>Main Content</h1>
</main>Why tabindex="-1" on <main>? <main> isn’t focusable by default in some browsers. Adding tabindex="-1" ensures programmatic focus works when the skip link is activated.
Breadcrumbs
Breadcrumbs should be wrapped in a <nav> with aria-label="Breadcrumb" and use aria-current="page" for the current page:
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/products/laptops">Laptops</a></li>
<li><a href="/products/laptops/gaming">Gaming Laptops</a></li>
<li aria-current="page">DodaTech Pro X1</li>
</ol>
</nav>Screen reader behavior: VoiceOver announces “Breadcrumb, 5 items, current page: DodaTech Pro X1, end of breadcrumb.” The aria-current="page" tells the user “you are here.”
/* Breadcrumb styling with separator (don't use > — it can be confusing) */
nav[aria-label="Breadcrumb"] ol {
list-style: none;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 4px;
}
nav[aria-label="Breadcrumb"] li:not(:last-child)::after {
content: "›";
margin-left: 4px;
color: #666;
}Responsive Hamburger Menus
Hamburger menus are problematic for accessibility when not implemented correctly. Here’s a fully accessible version:
<nav aria-label="Main navigation">
<button aria-expanded="false" aria-controls="nav-menu"
id="nav-toggle" class="nav-toggle">
<span class="sr-only">Menu</span>
<span class="hamburger-icon" aria-hidden="true">
<span></span><span></span><span></span>
</span>
</button>
<ul id="nav-menu" class="nav-menu" role="list" hidden>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/about">About</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<style>
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.nav-toggle {
display: none; /* shown on mobile via media query */
padding: 12px;
background: none;
border: none;
cursor: pointer;
}
@media (max-width: 768px) {
.nav-toggle { display: block; }
.nav-menu:not([hidden]) {
display: flex;
flex-direction: column;
}
}
</style>
<script>
const toggle = document.getElementById('nav-toggle');
const menu = document.getElementById('nav-menu');
toggle.addEventListener('click', () => {
const expanded = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute('aria-expanded', !expanded);
menu.hidden = expanded;
});
// Close menu on Escape
menu.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
menu.hidden = true;
toggle.setAttribute('aria-expanded', 'false');
toggle.focus();
}
});
</script>Key accessibility features:
aria-expandedtells screen readers whether the menu is open or closedaria-controlsassociates the button with the menu- The visible label is “Menu” (screen reader) while the hamburger icon is hidden with
aria-hidden - Escape closes the menu and returns focus
- The menu is hidden with the
hiddenattribute (notdisplay: nonetoggle in CSS)
aria-current for Active State
aria-current indicates the current item in a set — and it has several values:
| Value | Meaning | Use Case |
|---|---|---|
page | Current page in a set of pages | Navigation links, pagination |
step | Current step in a process | Multi-step forms, checkout |
location | Current item in a context | Breadcrumbs |
date | Current date in a set | Calendar widgets |
time | Current time in a set | Schedule |
true | Default — same as page | General current item |
<!-- aria-current="page" in navigation -->
<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>
</ul>
</nav>
<!-- aria-current="step" in a multi-step form -->
<nav aria-label="Checkout progress">
<ol>
<li aria-current="step">Cart</li>
<li>Shipping</li>
<li>Payment</li>
<li>Confirmation</li>
</ol>
</nav>Screen reader announcement: “Products, link, current page.” Users know exactly where they are.
Mega Menus
Mega menus (large dropdowns with multiple columns and groups) need careful keyboard handling:
<nav aria-label="Main">
<ul>
<li>
<button aria-expanded="false" aria-controls="products-menu">
Products
</button>
<div id="products-menu" role="region"
aria-label="Products submenu" hidden>
<div class="mega-grid">
<section aria-labelledby="heading-software">
<h3 id="heading-software">Software</h3>
<ul>
<li><a href="/doda-browser">Doda Browser</a></li>
<li><a href="/durga-antivirus">Durga Antivirus Pro</a></li>
<li><a href="/dodazip">DodaZIP</a></li>
</ul>
</section>
<section aria-labelledby="heading-services">
<h3 id="heading-services">Services</h3>
<ul>
<li><a href="/consulting">Consulting</a></li>
<li><a href="/support">Support</a></li>
<li><a href="/training">Training</a></li>
</ul>
</section>
</div>
</div>
</li>
<li><a href="/pricing">Pricing</a></li>
</ul>
</nav>Keyboard behavior for mega menus: Enter/Space toggles the menu. Escape closes it. Arrow keys move between items within the submenu. Tab moves to the next focusable element (exits the menu).
Search Accessibility
The search feature is one of the most important navigation tools:
<form role="search" aria-label="Site search">
<label for="search-input" class="sr-only">Search</label>
<input type="search" id="search-input" name="q"
placeholder="Search products, articles..."
aria-describedby="search-hint">
<button type="submit" aria-label="Search">
<span aria-hidden="true">🔍</span>
</button>
<p id="search-hint" class="sr-only">
Type your search term and press Enter
</p>
</form>
<script>
// Real-time search results with accessibility
const searchInput = document.getElementById('search-input');
const searchResults = document.getElementById('search-results');
searchInput.addEventListener('input', debounce(async () => {
const query = searchInput.value.trim();
if (query.length < 2) {
searchResults.innerHTML = '';
return;
}
const results = await fetchSearchResults(query);
if (results.length > 0) {
searchResults.innerHTML = `
<ul role="listbox" aria-label="Search results">
${results.map((r, i) => `
<li role="option" aria-selected="false">
<a href="${r.url}">${r.title}</a>
</li>
`).join('')}
</ul>
`;
// Announce result count
searchResults.setAttribute('aria-live', 'polite');
searchResults.innerHTML +=
`<span class="sr-only">${results.length} results found for "${query}"</span>`;
}
}, 300));
</script>Pagination
Pagination controls must convey the current page and allow keyboard navigation:
<nav aria-label="Pagination">
<ul class="pagination">
<li>
<a href="/blog/page/3" aria-label="Previous page"
rel="prev">« Previous</a>
</li>
<li><a href="/blog/page/1" aria-label="Page 1">1</a></li>
<li><a href="/blog/page/2" aria-label="Page 2">2</a></li>
<li><a href="/blog/page/3" aria-label="Page 3">3</a></li>
<li aria-current="page">
<span aria-label="Current page, page 4">4</span>
</li>
<li><a href="/blog/page/5" aria-label="Page 5">5</a></li>
<li>
<a href="/blog/page/5" aria-label="Next page" rel="next">
Next »
</a>
</li>
</ul>
</nav>Screen reader announcement: “Pagination, 5 pages, current page: page 4. Links: previous page, page 1, page 2, page 3, page 5, next page.”
Accessible Navigation Checklist
## Navigation Accessibility Checklist
### Landmarks
- [ ] `<header role="banner">` at top
- [ ] `<nav aria-label="Main">` for primary navigation
- [ ] `<main>` for primary content
- [ ] `<footer role="contentinfo">` at bottom
- [ ] All landmarks have unique labels if multiple of same type
### Skip Links
- [ ] Skip link is first focusable element
- [ ] Skip link is visible on focus
- [ ] Target element has `tabindex="-1"`
- [ ] Focus moves to main content on activation
### Links
- [ ] Link text describes the destination
- [ ] No "click here" or "read more" links
- [ ] Active page marked with `aria-current="page"`
- [ ] Same visual pages have same URL
### Dropdowns / Mega Menus
- [ ] Toggle has `aria-expanded` and `aria-controls`
- [ ] Submenu uses `role="region"` with `aria-label`
- [ ] Escape closes the menu
- [ ] Focus returns to toggle when menu closes
- [ ] Arrow keys navigate items within the menu
### Search
- [ ] Search form uses `role="search"`
- [ ] Search input has visible label on focus
- [ ] Results are announced via `aria-live`
- [ ] Search button has accessible name
### Pagination
- [ ] Wrapped in `<nav aria-label="Pagination">`
- [ ] Current page has `aria-current="page"`
- [ ] Previous/next have descriptive labels
- [ ] Page numbers clearly labeledCommon Navigation Mistakes
1. “Click Here” Links
“Click here” is meaningless to screen reader users who navigate by links. They hear “Click here, click here, Products, click here” — entirely unhelpful. Use descriptive anchor text.
2. Unlabeled Navigation Landmarks
Multiple <nav> elements without aria-label all show as “Navigation” — users can’t tell them apart.
3. Missing Active Page Indicator
Without aria-current="page", screen reader users can’t tell which page they’re on. The visual “active” state (bold text, underline) means nothing to them.
4. Hamburger Menu Without ARIA
A hamburger button without aria-expanded doesn’t tell screen readers whether the menu is open or closed.
5. Keyboard Traps in Dropdowns
Dropdowns that don’t close on Escape, or that trap Tab inside them, prevent keyboard users from moving past the navigation.
6. Breadcrumb Without Current Page
Breadcrumbs should end with the current page marked with aria-current="page", not as a link.
7. Mega Menus Not Closing on Blur
Mega menus that stay open when focus leaves require mouse interaction to close. Use blur events or focusout listeners.
Practice Questions
1. What does aria-current="page" do and where should it be used?
It indicates the current page in a navigation set. Screen readers announce “current page” after the link text. Use it on the <a> or <span> representing the current page in nav, breadcrumbs, or pagination.
2. Why should you use aria-label on <nav> elements?
When you have multiple navigations (main nav, footer nav, breadcrumb), aria-label gives each a unique name so screen reader users can distinguish them.
3. What are the keyboard requirements for a responsive hamburger menu?
Enter/Space toggles the menu, Escape closes it, focus returns to the toggle button after closing, and aria-expanded reflects the state.
4. Why is it important that skip links are the first focusable element?
Because pressing Tab on page load should immediately offer the option to skip navigation. If anything comes before the skip link, keyboard users waste keystrokes.
5. Challenge: Build a fully accessible mega menu with at least 3 sections (each with sub-items). It should open on button click (not hover), navigate with arrow keys, close on Escape, work with keyboard only, and have proper ARIA attributes throughout.
Real-World Task
Audit your website’s navigation. Check: all links have descriptive text, landmarks are labeled, the current page is indicated with aria-current, dropdowns are keyboard accessible, the search is properly labeled, and breadcrumbs mark the current page. Fix all issues.
FAQ
Try It Yourself
Let’s build a navigation accessibility testing tool:
// nav-audit.js — audit navigation accessibility
function auditNavigation() {
const issues = [];
// Check landmarks
const navs = document.querySelectorAll('nav');
navs.forEach(nav => {
const label = nav.getAttribute('aria-label');
const labelledby = nav.getAttribute('aria-labelledby');
if (!label && !labelledby) {
issues.push({
type: 'warning',
element: nav.outerHTML.substring(0, 60),
message: '<nav> without aria-label — screen readers can\'t distinguish it'
});
}
});
// Check links
const links = document.querySelectorAll('a');
links.forEach(link => {
const text = link.textContent.trim();
const href = link.getAttribute('href');
if (text === '' && !link.querySelector('img[alt]')) {
issues.push({
type: 'critical',
element: href,
message: 'Empty link — screen reader can\'t determine purpose'
});
}
if (/^(click here|read more|learn more|link)$/i.test(text)) {
issues.push({
type: 'critical',
element: href,
message: `Generic link text: "${text}" — no context about destination`
});
}
});
// Check skip links
const skipLink = document.querySelector('a[href^="#"]:first-child');
if (!skipLink || !skipLink.className.includes('skip')) {
issues.push({
type: 'info',
element: 'page top',
message: 'No skip link detected — keyboard users must tab through all navigation'
});
}
// Check active page indicators
const currentLinks = document.querySelectorAll('[aria-current]');
if (currentLinks.length === 0) {
issues.push({
type: 'info',
element: 'navigation',
message: 'No aria-current="page" found — current page not indicated to screen readers'
});
}
console.table(issues);
return issues;
}
auditNavigation();Expected output: A console table showing all navigation issues found, categorized by severity (critical, warning, info).
What’s Next
Congratulations on completing this Accessible Navigation tutorial! Here’s where to go from here:
- Practice daily — Test navigation on every page you build
- Build a project — Create an accessible navigation component library
- Explore related topics — Learn accessibility testing next
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro