DEV Community

Cover image for How I Implement Accessible Navigation Menus: Principles, Techniques, and Modern Best Practices
Ellis Pike
Ellis Pike

Posted on

How I Implement Accessible Navigation Menus: Principles, Techniques, and Modern Best Practices

I’ve built lots of websites over the years. The navigation menus always feel like the backbone to me. They set the tone for how people find my stuff. But it’s easy to leave people out if you don’t think about accessibility. People who use keyboards, screen readers, or assistive technologies often run into trouble. By 2025, it’s not just a good idea for me to make sure everyone can use my menus. It’s my responsibility, and sometimes the law expects it too.

Note: This piece was written with artificial intelligence support and may reference projects I'm affiliated with.

Let me walk you through my approach for building accessible navigation menus. I’ll explain why each step matters. I’ll share hands-on techniques and code that actually works. This will help you support all users, whatever their device or ability.

The Foundations: Why Accessible Navigation Matters to Me

Accessible websites let people of all abilities move around, understand, and use what I’ve built. For navigation, that means I need to make sure:

  • Keyboard users can reach every menu item without needing a mouse.
  • Screen readers and assistive tech give good details about links and which menu items are open or active.
  • Visual users see menus with strong contrast and obvious focus highlights.
  • The menu always behaves in a predictable way on any screen size.

I’ve realized accessibility is not just about permanent disabilities. It helps people with temporary or even situational needs too. Every time I build a menu that works for more people, I end up making it easier for me and all my users.

Structuring My Menu with Semantic HTML

Before I add interactivity, I always start with clean, semantic HTML. This is the solid ground for accessibility. My default menu usually looks like this:

<nav aria-label="Primary">
  <ul>
    <li><a href="/home" aria-current="page">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/features">Features</a></li>
    <li><a href="/pricing">Pricing</a></li>
    <li><a href="/login" class="accent">Login</a></li>
  </ul>
</nav>
Enter fullscreen mode Exit fullscreen mode

What I always keep in mind:

  • I use <nav> for navigation areas, always.
  • I add a clear aria-label when I have more than one menu on a page.
  • I use lists (<ul> and <li>) to organize my links. It makes the structure clear.
  • I mark the current page link with aria-current="page" so screen readers can announce it.
  • I use anchor tags for real navigation links, not buttons or divs.

Styling for Usability and Responsiveness

CSS isn’t just about making things look pretty for me. It needs to make the menu clear and easy to use.

  • I mostly use display: flex for layout. Flexbox keeps my order natural and tab-friendly.
  • I never set random sizes. Instead, I use padding to space out the links. Content sets the size.
  • I make sure my color choices have high contrast so text stands out.
  • I highlight the current page, and I make focus states super obvious for keyboard users.
  • Responsive design is huge. When I shrink the screen, my menu switches to a mobile-friendly version, usually a hamburger menu. I do this with media queries at around 700px.

Here’s a bit of my CSS for responsive menus:

nav ul {
  list-style: none;
  display: flex;
  margin: 0;
  padding: 0;
}

nav a:focus-visible,
nav a:hover {
  outline: 2px solid var(--accent-color);
  background: var(--focus-bg, #222);
}

@media (max-width: 700px) {
  nav ul {
    flex-direction: column;
    position: fixed;
    right: -100%;
    width: 250px;
    top: 0;
    height: 100%;
    transition: right 0.3s;
  }
  nav ul.show {
    right: 0;
  }
}
Enter fullscreen mode Exit fullscreen mode

How I Implement Interactive and Responsive Menus

Menus these days aren't static. Most projects want a hamburger button and a sliding menu for small screens. So here’s what I do to keep everything accessible.

Menu Toggle Buttons

My hamburger buttons and close buttons are:

  • Always real <button> elements, not clickable divs or spans.
  • Descriptive, using aria-label like aria-label="Open menu" or aria-label="Close menu".
  • Tied to the menu with aria-controls and menu state with aria-expanded.

Example:

<!-- Hamburger Button -->
<button 
  aria-label="Open menu" 
  aria-controls="main-menu" 
  aria-expanded="false" 
  id="menu-toggle">
  <!-- SVG icon here -->
</button>
Enter fullscreen mode Exit fullscreen mode

When I open the menu, I set aria-expanded to true with JavaScript.

My JavaScript for Toggling Menus

I use class toggling and update ARIA labels every time:

const navMenu = document.getElementById('main-menu');
const menuToggle = document.getElementById('menu-toggle');

function openMenu() {
  navMenu.classList.add('show');
  menuToggle.setAttribute('aria-expanded', 'true');
}

function closeMenu() {
  navMenu.classList.remove('show');
  menuToggle.setAttribute('aria-expanded', 'false');
}

menuToggle.addEventListener('click', () => {
  if (navMenu.classList.contains('show')) {
    closeMenu();
  } else {
    openMenu();
  }
});
Enter fullscreen mode Exit fullscreen mode

Overlay and Close on Exit

When I use slide-out menus, I show a semi-transparent overlay behind the menu. The overlay:

  • Fades out the background so the menu is easier to focus on.
  • Lets users click outside to close the menu.
  • Gets hidden from screen readers with aria-hidden="true".

Here’s my typical overlay setup:

<div id="overlay" aria-hidden="true"></div>
Enter fullscreen mode Exit fullscreen mode

CSS for the overlay:

#overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  z-index: 9;
}
nav ul.show ~ #overlay {
  display: block;
}
Enter fullscreen mode Exit fullscreen mode

Whenever I open the menu, I show this overlay. When the menu closes, I hide it again.

Focus Management and Keyboard Navigation

This part is critical for me. Keyboard users should have a great experience every time. I make sure that:

  • Anyone can hop through the menu using just the Tab key.
  • Menu links only get focus when the menu is actually visible.
  • It’s impossible to get lost or trapped in the menu.

How I Use the inert Attribute (Modern Method)

When a menu is hidden, I use inert to remove it from keyboard and assistive tech navigation:

function updateMenuAccessibility(isOpen) {
  if (isOpen) {
    navMenu.removeAttribute('inert');
  } else {
    navMenu.setAttribute('inert', '');
  }
}
Enter fullscreen mode Exit fullscreen mode

I keep an ear out for window resizes since the menu’s visibility can change on different devices or screen sizes.

Skip Links

This is one of my secret weapons. I add a skip link right at the top to let people jump straight to content.

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

I make this only visible on focus:

.skip-link {
  position: absolute;
  left: -1000px;
}
.skip-link:focus {
  left: 10px;
  top: 10px;
  background: #fff;
  color: #000;
  padding: 8px 16px;
  z-index: 1000;
}
Enter fullscreen mode Exit fullscreen mode

If you’re looking for a way to save time building accessible, cross-platform navigation and UI components without losing flexibility, I recommend checking out gluestack. It’s a comprehensive open-source React and React Native components library that lets you pick only the pieces you need, ensures ARIA and keyboard best practices, and offers seamless styling with Tailwind CSS or NativeWind. This way, you can focus on your app while ensuring consistency and accessibility across the web, Next.js, and mobile with Expo.

Advanced Accessibility for Submenus and Mega Menus

Dropdowns and mega menus take more work. Here’s what I do:

ARIA for Submenus

  • I use aria-haspopup="true" on parent menu items with submenus.
  • I manage submenu state with aria-expanded every time it opens or closes.
  • For custom dropdowns that use arrow keys, I use roles like role="menu" and role="menuitem".

Keyboard Trapping and Escape

When a submenu opens, I keep keyboard focus inside it. Users can close with Escape. Focus returns to the button that opened it.

Here’s a focus trap I use:

function trapFocus(menu) {
  const focusable = menu.querySelectorAll('a, button');
  const first = focusable[0];
  const last = focusable[focusable.length - 1];

  menu.addEventListener('keydown', e => {
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault();
        last.focus();
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault();
        first.focus();
      }
    }
    if (e.key === 'Escape') {
      closeMenu();
      triggerButton.focus();
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

Visual and Contrast Considerations

Accessibility isn’t just tech for me. It’s about what people see.

  • I keep contrast high for text and backgrounds, especially for hover and active states.
  • I never rely on mouse hover alone. If a keyboard user can’t open a menu, it’s not good enough.
  • I add clear focus indicators using :focus-visible so keyboard users know exactly where they are.

Avoiding Common Accessibility Pitfalls

  • I do not use divs or spans for menu links. I always choose semantic elements.
  • I keep away from positive tabindex values. I stick to 0 or -1 for predictable order.
  • I never hide menus visually while leaving them reachable with Tab. I use inert, or set them to display: none or visibility: hidden when they’re closed.
  • I line up the DOM order with the visual order. That way, the experience is consistent for everyone.
  • I always test with a keyboard and a screen reader, even if it’s just for a few minutes.

Summary: Accessibility Is an Ongoing Effort

Building accessible navigation menus is a blend of solid HTML, smart interactivity, and clear visuals. It takes ongoing attention and testing, listening to feedback, and learning what's new. Every time I improve ARIA labels or make the menu easier with the keyboard, someone out there benefits. It’s worth it.

FAQ

What do I think is the most important first step for accessible navigation?

I always start with semantic HTML using the <nav> tag and real anchor links. That good structure is the foundation for both keyboard and screen reader accessibility.

How do I make sure my navigation menu is keyboard-friendly?

I check that every link and button is reachable using the Tab key. The focus needs to move in the same order as the menu. I also make sure there’s a clear focus outline. If I have menus that open or close, I control which elements get focus and hide others from tabbing.

What ARIA attributes do I use for navigation menus?

I add aria-label to my <nav>, aria-current="page" on the active link, aria-expanded and aria-controls for buttons that open or close menus, and aria-hidden on overlays. If I have submenus, I use aria-haspopup and manage submenu visibility with aria-expanded.

How do I test if my menu is actually accessible?

I try navigating the site using only my keyboard. Then I use a screen reader to listen to what it actually says. I look for clear focus rings and follow the tab order to see if it matches my expectations. Extensions and online tools help me spot missing ARIA tags or contrast problems as well.


For me, accessible navigation is not just a checklist. It’s a mindset that shapes how I design and build. The more I practice, the more inclusive and professional my results become. Good luck, and happy coding!

Top comments (0)