Skip to content
←Back to blog
WCAGhow-tonavigation

Accessible navigation menus: a practical guide

How to build an accessible navigation menu: nav landmarks, aria-current, disclosure dropdowns, mobile menu buttons and the WCAG criteria that apply.

P

Pavel Charkasau

An accessible navigation menu is a list of ordinary links inside a labelled <nav> element, with the current page marked by aria-current="page", and any dropdowns opened by a real <button> that reports its state with aria-expanded. That is the whole pattern the W3C recommends for website navigation. Its disclosure navigation example deliberately does not use the ARIA menu role, "because it does not provide the complex functionality that assistive technologies expect in a widget that has the menu role" (W3C ARIA APG). The keyboard model is plain Tab, Enter or Space to open a submenu, and Escape to close it. This guide covers the markup, then the places real menus break: hover dropdowns, the mobile hamburger button and sticky headers.

What makes an accessible navigation menu?

A screen reader user can jump straight to it, hear where they are, and move through it with the keys they already use. A keyboard user can see focus and open every submenu without a mouse.

Broken menus usually fail in familiar ways. The dropdown only opens on :hover. The trigger is a <div> or an <a href="#"> with a click handler. Or someone added role="menubar" and role="menuitem" because it sounded right, and now screen readers announce an application menu that doesn't behave like one.

How do I mark up a basic navigation menu?

Start with the boring version:

<nav aria-label="Main">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/pricing" aria-current="page">Pricing</a></li>
    <li><a href="/docs">Docs</a></li>
    <li><a href="/blog">Blog</a></li>
  </ul>
</nav>

The <nav> element creates a navigation landmark, so screen reader users can jump to it from a landmarks list. The W3C menus tutorial says to identify the menu "ideally using the HTML5 <nav> element to allow users access to the menu directly" (W3C WAI).

The aria-label names it. That matters once you have more than one: a header menu, a footer menu and a docs sidebar all announce as "navigation" unless you tell them apart. MDN notes a document "may have several <nav> elements" and recommends labelling them (MDN). Don't put the word "navigation" in the label. Screen readers already say it, so "Main navigation navigation" is what you'd get.

The <ul> tells the user how many items there are ("list, 4 items"), and the W3C tutorial recommends it for website navigation (W3C WAI).

aria-current="page" marks where the user is. The tutorial names this attribute for the job (W3C WAI). Hang the visible style on the same attribute so the two never drift apart: nav a[aria-current="page"] { font-weight: 700; text-decoration: underline; }. Not every link group needs a <nav> either; MDN says it is "intended only for a major block of navigation links" (MDN).

Should I use role="menu" or role="menubar" for site navigation?

No. It's a common mistake, and usually a well-meant one.

The ARIA menu role describes "a widget that offers a list of choices to the user, such as a set of actions or functions", like the File menu in a desktop app (W3C ARIA APG). A screen reader user who hears "menu bar" expects arrow keys to move between items, typeahead to work, and Tab to leave the whole widget. Add the roles without that behaviour and users are told one thing and handed another. The W3C's application menus tutorial warns that adding the roles "does not automatically enable the menu's functionality or keyboard behavior" (W3C WAI).

The APG's disclosure navigation example states the reason plainly: "Typical site navigation does not need all the keyboard interactions specified by the menu and menubar pattern" (W3C ARIA APG). The APG does publish a navigation menubar example, so it isn't forbidden. My view: for a marketing site or a SaaS header, don't. You take on a full keyboard model to maintain, and users get a menu that behaves unlike the rest of the web.

A role="menu" without menuitem children fails the axe-core rule aria-required-children, which maps to 1.3.1 (Deque University).

How do I build an accessible dropdown submenu?

Use the disclosure pattern. A <button> toggles a list of links, and aria-expanded says whether it's open:

<nav aria-label="Main">
  <ul>
    <li>
      <button type="button" aria-expanded="false" aria-controls="sub-product">
        Product
      </button>
      <ul id="sub-product" hidden>
        <li><a href="/scan">Website scanner</a></li>
        <li><a href="/monitoring">Monitoring</a></li>
      </ul>
    </li>
    <li><a href="/pricing">Pricing</a></li>
  </ul>
</nav>
nav.addEventListener("click", (e) => {
  const btn = e.target.closest("button[aria-expanded]");
  if (!btn) return;
  const open = btn.getAttribute("aria-expanded") === "true";
  btn.setAttribute("aria-expanded", String(!open));
  document.getElementById(btn.getAttribute("aria-controls")).hidden = open;
});

A <button> gives you focus, Enter and Space activation, and the right role for free. The screen reader hears "Product, button, collapsed", then "expanded" after activation. The APG uses exactly these attributes: aria-expanded to report whether the container is visible, and aria-controls to point at it (W3C ARIA APG).

Add Escape to close an open dropdown, as the APG keyboard table does, and return focus to its button. Arrow keys, Home and End are listed as optional.

Don't open submenus on focus. The W3C fly-out tutorial is clear on this: "Submenus should not open when using the tab key to navigate through the menu, as keyboard users would then have to step through all submenu items to get to the next top-level item" (W3C WAI).

What if the top-level item is also a link?

Then it needs two controls. If "Product" goes to /product and also has a submenu, one element can't do both jobs. The W3C tutorial's answer is a separate button next to the link to open and close the submenu (W3C WAI). Give that button a name that says what it opens, like aria-label="Product submenu", otherwise screen reader users hear a row of identical, unnamed chevrons.

What about dropdowns that open on hover?

You can keep hover for mouse users, but hover can't be the only way in, and the dropdown has to meet WCAG 1.4.13 Content on Hover or Focus (Level AA). The Understanding document names "sub-menus" as content the criterion covers (W3C). Three conditions apply:

  • Dismissible. The user can close it without moving the pointer or focus. Escape is the usual answer.
  • Hoverable. The pointer can move from the trigger onto the dropdown without it vanishing. A gap of a few pixels between the two, with nothing bridging it, fails this for anyone with a shaky hand.
  • Persistent. It stays open until the user leaves, dismisses it, or it's no longer relevant. No closing on a timer while the pointer is still over it.

The W3C fly-out example adds a one-second delay before a submenu closes after the mouse leaves, because pointer movement "is not very precise" (W3C WAI). That small delay helps real users more than most ARIA.

How do I make a mobile hamburger menu accessible?

The hamburger icon is a disclosure button, so the same rules apply, with a few extra mistakes to avoid:

<button type="button" aria-expanded="false" aria-controls="mobile-nav">
  <svg aria-hidden="true" focusable="false"><!-- three lines --></svg>
  <span class="visually-hidden">Menu</span>
</button>
  • Give it a name. An icon-only button with no text is announced as "button" and nothing else. That fails 4.1.2 Name, Role, Value, and axe-core reports it as button-name.
  • Keep the name stable. Let aria-expanded report the state; don't also swap the label to "Close menu".
  • Size the target. WCAG 2.2's 2.5.8 Target Size (Minimum) asks for at least 24 by 24 CSS pixels, or enough spacing around smaller targets (W3C). A 16-pixel icon with no padding misses.
  • Decide if it's modal. A full-screen menu should behave like a dialog: move focus in, keep it there, return it on close (see our modals and dialogs guide). A menu that pushes content down is a plain disclosure.

Do sticky headers cause accessibility problems?

They can. When a user tabs down the page, a sticky header or cookie banner can sit on top of the link that has focus. WCAG 2.2's 2.4.11 Focus Not Obscured (Minimum), Level AA, requires that a focused component "is not entirely hidden due to author-created content", and the Understanding document names sticky headers and footers as typical causes (W3C). A one-line fix covers most cases: html { scroll-padding-top: 5rem; }, set to your header's height.

Also check that focus is visible on every menu item (2.4.7 Focus Visible). A global outline: none is the usual culprit.

Which WCAG criteria apply to navigation menus?

CriterionLevelWhat it means for a menu
2.1.1 KeyboardAEvery link and submenu works without a mouse
2.4.1 Bypass BlocksAUsers can skip the repeated menu, usually with a skip link
4.1.2 Name, Role, ValueAToggles are buttons with a name and an aria-expanded state
1.4.13 Content on Hover or FocusAAHover dropdowns are dismissible, hoverable and persistent
2.4.7 Focus VisibleAAYou can see which item has focus
3.2.3 Consistent NavigationAAThe menu keeps the same relative order across pages
2.4.11 Focus Not Obscured (Minimum)AA, 2.2 onlySticky headers don't hide the focused item
2.5.8 Target Size (Minimum)AA, 2.2 onlyMenu toggles are at least 24 by 24 CSS pixels, or spaced

3.2.3 requires repeated navigation to "occur in the same relative order each time" (W3C), so a header that reorders items per section fails it.

Which version you are held to depends on the law. 2.4.11 and 2.5.8 are new in WCAG 2.2 (W3C WAI). The DOJ's ADA Title II rule adopts WCAG 2.1 Level AA (ADA.gov), and EN 301 549 v3.2.1, the standard behind the EAA, carries over WCAG 2.1 for web content (ETSI). I'd build to 2.2 anyway. A 24-pixel button costs nothing.

What can a scanner catch in a menu, and what can't it?

A scanner catches the structural problems: an unnamed hamburger button (button-name), two navigation landmarks with the same name (landmark-unique, which Deque classes as a best practice rather than a WCAG failure, Deque University), menu roles missing their required children, low-contrast link text. Our scanner runs these checks against the rendered page and gives you the selector for each.

It can't tell whether focus gets lost when a dropdown closes, or whether the dropdown vanishes as the pointer crosses a gap. Deque's study of more than 2,000 audits found automated testing identified about 57% of issues by volume (Deque), and the share is lower if you count success criteria instead. The W3C says evaluation tools "can not determine accessibility, they can only assist in doing so" (W3C WAI). Full conformance needs a person to check.

The manual test takes five minutes. Unplug the mouse and tab through the header: can you see focus, open each submenu with Enter, and close it with Escape back onto its button? Then turn on a screen reader, pull up the landmarks list, and listen for the label, "collapsed" and "expanded", and "current page". Our screen reader testing guide has the key commands, and the WCAG checklist lists the criteria above.

FAQ

Should a navigation menu use role="menu"?

No, not for typical website navigation. The W3C's example uses a <nav> with links and disclosure buttons, because the menu role promises an application-style keyboard model.

How do I mark the current page in a navigation menu?

Add aria-current="page" to that link. Screen readers announce it, and you can style the same attribute in CSS.

Should dropdown menus open on hover or on click?

Both can work, but a click or keyboard trigger must always exist. If the dropdown also opens on hover, WCAG 1.4.13 requires it to be dismissible with Escape, to stay open while the pointer moves onto it, and not to close on its own.

Do I need aria-label on the nav element?

You need it when the page has more than one navigation landmark, so labels like "Main" and "Footer" tell them apart. Leave "navigation" out of the label.

Does a hamburger menu button need a text label?

Yes. Without an accessible name, such as visually hidden text reading "Menu", screen readers announce it as just "button". Add aria-expanded too.

Check the menu on your site

Your navigation is on every page, so one unnamed button shows up everywhere. Run a free scan to find the structural issues with their selectors, then spend five minutes tabbing through the header with the mouse unplugged. The scan finds markup problems; the keyboard test tells you whether people can use the menu.


Pavel Charkasau, founder, wcagc.com. Last updated 29 September 2026.

Sources