How to Create an Accessible Navigation Menu: A Step-by-Step Guide for Developers

Navigation is the backbone of every website, yet it remains one of the most commonly broken patterns when it comes to accessibility. A poorly built menu can lock out keyboard users, confuse screen readers, and create real legal risk under WCAG 2.2. The good news? Building an accessible navigation menu is not difficult once you understand the right combination of semantic HTML, ARIA, and focus management.

This guide is a hands-on walkthrough with copy-paste code for skip links, dropdowns, and mobile hamburger menus. Every snippet has been tested against modern screen readers (NVDA, JAWS, VoiceOver) and aligns with WCAG 2.2 success criteria.

Why Accessible Navigation Matters in 2026

With WCAG 2.2 now the global baseline and the European Accessibility Act enforced since June 2025, navigation menus are under more scrutiny than ever. Beyond compliance, accessible menus deliver:

  • Better UX for everyone, including mobile and one-handed users
  • Improved SEO through clean semantic markup
  • Lower bounce rates from users relying on assistive tech
  • Reduced legal exposure from accessibility lawsuits
website navigation menu code

Step 1: Start With a Skip Link

Before the menu itself, give keyboard users a way to bypass it. A skip link is the very first focusable element on the page.

<a class="skip-link" href="#main">Skip to main content</a>

<style>
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: #000;
  color: #fff;
  padding: 12px 16px;
  z-index: 100;
}
.skip-link:focus {
  left: 8px;
  top: 8px;
  outline: 3px solid #ffbf00;
}
</style>

WCAG 2.2 tip: Success Criterion 2.4.11 (Focus Not Obscured) requires that focused elements remain visible. Make sure your skip link is not hidden behind sticky headers when focused.

Step 2: Use Semantic HTML for the Menu Structure

Screen readers rely on landmarks. Always wrap navigation in a <nav> element and use a real list. Avoid <div> soup.

<nav aria-label="Main">
  <ul class="menu">
    <li><a href="/">Home</a></li>
    <li><a href="/services">Services</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

When to Use aria-label vs aria-labelledby

Scenario Recommended Attribute
One nav on the page None needed (default landmark works)
Multiple nav landmarks aria-label="Main", aria-label="Footer"
Visible heading already exists aria-labelledby="nav-heading-id"
website navigation menu code

Step 3: Build an Accessible Dropdown

Dropdowns are where most menus fail. The trigger should be a real <button>, not a link with href="#".

<li class="has-dropdown">
  <button aria-expanded="false" aria-controls="submenu-services">
    Services
    <span aria-hidden="true">▾</span>
  </button>
  <ul id="submenu-services" hidden>
    <li><a href="/web">Web Design</a></li>
    <li><a href="/seo">SEO</a></li>
    <li><a href="/audit">Accessibility Audit</a></li>
  </ul>
</li>

And the JavaScript to handle toggling, Escape key, and outside clicks:

document.querySelectorAll('.has-dropdown > button').forEach(btn => {
  const submenu = document.getElementById(btn.getAttribute('aria-controls'));

  btn.addEventListener('click', () => {
    const open = btn.getAttribute('aria-expanded') === 'true';
    btn.setAttribute('aria-expanded', String(!open));
    submenu.hidden = open;
  });

  btn.parentElement.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
      btn.setAttribute('aria-expanded', 'false');
      submenu.hidden = true;
      btn.focus();
    }
  });
});

document.addEventListener('click', (e) => {
  document.querySelectorAll('.has-dropdown').forEach(item => {
    if (!item.contains(e.target)) {
      const btn = item.querySelector('button');
      const submenu = item.querySelector('ul');
      btn.setAttribute('aria-expanded', 'false');
      submenu.hidden = true;
    }
  });
});

Required Keyboard Behavior

Key Expected Action
Tab Move to next focusable element
Shift + Tab Move to previous focusable element
Enter / Space Toggle dropdown
Escape Close dropdown and return focus to trigger
Arrow Down Open dropdown and move to first item (optional)

Step 4: Build an Accessible Mobile Hamburger Menu

The hamburger button needs a clear accessible name and proper state communication.

<button id="menu-toggle"
        aria-expanded="false"
        aria-controls="primary-menu"
        aria-label="Open main menu">
  <span class="bar" aria-hidden="true"></span>
  <span class="bar" aria-hidden="true"></span>
  <span class="bar" aria-hidden="true"></span>
</button>

<nav id="primary-menu" aria-label="Main" hidden>
  <!-- menu items -->
</nav>
const toggle = document.getElementById('menu-toggle');
const menu = document.getElementById('primary-menu');

toggle.addEventListener('click', () => {
  const open = toggle.getAttribute('aria-expanded') === 'true';
  toggle.setAttribute('aria-expanded', String(!open));
  toggle.setAttribute('aria-label', open ? 'Open main menu' : 'Close main menu');
  menu.hidden = open;

  if (!open) {
    menu.querySelector('a, button').focus();
  }
});

WCAG 2.2 tip: Make sure your hamburger button is at least 24×24 CSS pixels (Success Criterion 2.5.8 Target Size Minimum). We recommend 44×44 for comfort.

website navigation menu code

Step 5: Manage Focus Properly

Focus management is what separates a good menu from a great one. Follow these rules:

  1. Never remove focus outlines without replacing them with something more visible
  2. Trap focus inside open mobile menus if they cover the full viewport
  3. Return focus to the trigger button when a menu closes
  4. Test with Tab only, then with a screen reader, then on a real mobile device

Custom focus style example:

:focus-visible {
  outline: 3px solid #0066ff;
  outline-offset: 2px;
  border-radius: 2px;
}

Step 6: Test Your Accessible Navigation Menu

Automated tools catch about 30% of issues. Combine them with manual testing:

  • Automated: axe DevTools, Lighthouse, WAVE
  • Keyboard: Unplug your mouse and navigate the entire menu
  • Screen reader: NVDA on Windows, VoiceOver on macOS or iOS
  • Zoom: Test at 200% and 400% zoom (WCAG 1.4.10)
  • Reduced motion: Respect prefers-reduced-motion for menu animations
website navigation menu code

Common Mistakes to Avoid

  • Using <div onclick> instead of <button>
  • Adding role="menu" and role="menuitem" to a website navigation (those roles are for desktop-app menubars)
  • Forgetting aria-expanded on toggle buttons
  • Hiding submenus with display:none only on hover (breaks keyboard access)
  • Using icon-only buttons without an accessible name

FAQ

Should I use role=”menubar” for my website navigation?

No. The menubar role is designed for application menus that mimic desktop software. For standard website navigation, a <nav> element with a list is the right pattern and is recommended by the WAI ARIA Authoring Practices Guide.

Do dropdowns need to open on hover?

Hover is fine as an enhancement, but the dropdown must also work on click and keyboard activation. Hover-only menus fail WCAG 2.1.1 (Keyboard) and 2.5.3 (Label in Name).

Is a hamburger menu accessible?

Yes, when built correctly with a real button, an accessible name, aria-expanded, and proper focus management. The pattern itself is fine, the implementation is what usually fails.

What is the minimum target size for menu items under WCAG 2.2?

Success Criterion 2.5.8 requires a minimum of 24×24 CSS pixels for interactive targets, with some exceptions. We recommend 44×44 pixels for better usability on touch devices.

Do I need JavaScript for an accessible menu?

For simple flat navigation, no. For dropdowns with proper state management, yes. CSS-only dropdowns using :hover or :focus-within can work but often fail keyboard expectations on mobile and with assistive tech.

Final Thoughts

Building an accessible navigation menu is not about checking boxes, it is about making sure every visitor can actually use your site. Start with semantic HTML, layer ARIA only when needed, manage focus deliberately, and test with real assistive technologies. Your users, your SEO, and your legal team will thank you.

Need an audit of your existing navigation? Get in touch with our accessibility team for a free initial review.