Skip to content
Pandus

Accessible navigation menu

Updated October 14, 2026 · 6 min read

The main navigation is the first thing every visitor uses, and on a shop it often grows into a mega-menu with categories, brands and promotions. The most common accessibility mistake is not missing ARIA but the wrong ARIA: role="menubar" copied from a component library. This page explains what the APG recommends instead. It is part of the accessible UI patterns series.

The APG has a Menu and Menubar pattern for application menus — "a list of choices, such as actions or functions" that behaves like menus in desktop software. Its navigation menubar example carries an explicit caution:

"The menubar pattern requires complex functionality that is unnecessary for typical site navigation" … "A pattern more suited for typical site navigation with expandable groups of links is the Disclosure Pattern."

The APG adds that the disclosure pattern "is better suited for most web sites because few sites need the additional keyboard functionality". Its disclosure navigation example does not use the menu role because it "does not provide the complex functionality that assistive technologies expect". A role="menu" is a promise of desktop-style menu behaviour; if the site does not deliver it, the menu is harder to use than plain links.

Recommendation: a nav landmark, nested lists of links, and disclosure buttons for the groups.

Relevant WCAG criteria

Code example

<nav aria-label="Main">
  <ul>
    <li>
      <button type="button" aria-expanded="false" aria-controls="nav-women">Women</button>
      <ul id="nav-women" hidden>
        <li><a href="/en/women/dresses">Dresses</a></li>
        <li><a href="/en/women/shoes" aria-current="page">Shoes</a></li>
      </ul>
    </li>
    <li><a href="/en/sale">Sale</a></li>
  </ul>
</nav>

<script>
  document.querySelectorAll('nav button[aria-expanded]').forEach((button) => {
    const list = document.getElementById(button.getAttribute('aria-controls'));
    button.addEventListener('click', () => {
      const open = button.getAttribute('aria-expanded') === 'true';
      button.setAttribute('aria-expanded', String(!open));
      list.hidden = open;
    });
  });
</script>

Notes:

  • If the top item must also be a link to the category page, the APG has a variant, "Disclosure Navigation Menu with Top-Level Links": the link and a separate toggle button sit next to each other. Do not make one element both a link and a toggle.
  • Add Escape: close the open group and return focus to its button, as the APG example does.
  • On mobile, the "hamburger" button is the same disclosure: aria-expanded, a name such as "Menu", and the panel follows it in the DOM.

Keyboard interaction

From the APG disclosure navigation example:

Key Function
Tab / Shift + Tab Moves through the top-level buttons and links and, when a group is open, through its links
Enter or Space on a button Opens or closes the group
Enter on a link Follows the link
Escape Closes the open group and returns focus to its button
Arrow keys, Home, End Optional: move between buttons and links; they supplement Tab rather than replace it

A true menubar would instead require arrow keys to move between items, Tab to leave the whole menubar and type-ahead by first letter — the "complex functionality" the APG means.

Common mistakes on online shops

  • Mega-menus that open only on mouse hover; keyboard users Tab through the top links and never reach the sub-categories.
  • role="menubar" and role="menuitem" on ordinary links, often from a theme or plugin, without the arrow-key behaviour.
  • Hidden sub-menus that are still focusable: Tab disappears into invisible links (focus lost behind opacity: 0).
  • The category name is both a link and the toggle, so touch and keyboard users cannot open the sub-menu.
  • No aria-expanded, or it never changes from false.
  • Several nav landmarks without labels (main, footer, account), so screen reader users cannot tell them apart.
  • Hamburger buttons without a name and mobile menus that do not trap or return focus when built as a full-screen overlay.

What automated checks catch vs manual testing

Automated (axe-core rule) What it finds
link-name, button-name Menu links and toggle or hamburger buttons without a name
aria-required-children, aria-required-parent menu/menubar without menuitem children, menuitem outside a menu
aria-allowed-role Roles that do not fit the element (advice)
aria-hidden-focus Links inside an aria-hidden sub-menu that can still receive focus
list, listitem Broken list structure in the navigation
landmark-unique Several navigation landmarks with the same or no name (advice)
bypass, skip-link No way to skip the menu; a skip link with a broken target
target-size Menu targets that are too small

All are cards in our rule library. The scan sees the page as it loads, with sub-menus closed. Manual testing: open every group with Enter or Space, check that aria-expanded changes, press Escape, Tab through the open group, check that closed groups cannot receive focus, and repeat with the mobile menu at 320 CSS pixels width. The browser extension can check the page with a group open.

Run the free one-page check on your home page. It is an automated check, not a full audit.

Frequently asked questions

Should a website navigation use role="menu" or role="menubar"?

Usually not. The APG warns that the menubar pattern requires complex functionality that is unnecessary for typical site navigation and recommends the disclosure pattern for navigation with expandable groups of links.

What is disclosure navigation?

A nav element with a list of links, where each group with sub-links has a button with aria-expanded that shows or hides the sub-list. Users move through it with Tab, Enter and Space, like the rest of the page.

How do I mark the current page in the menu?

Put aria-current="page" on the link to the page the user is on, and show it visually as well, not by colour alone.

Does the menu need arrow-key support?

Not in the disclosure pattern. The APG example supports arrow keys, Home and End as an optional extra on top of Tab. A real menubar, in contrast, must implement the full arrow-key model.

Can an automated check test a navigation menu?

It finds links and buttons without names, menu roles without menu items, duplicate unlabelled navigation landmarks and hidden but focusable links. It cannot test whether sub-menus open with the keyboard or whether aria-expanded updates.

Sources

  • W3C WAI, APG Navigation Menubar example (caution about site navigation): https://www.w3.org/WAI/ARIA/apg/patterns/menubar/examples/menubar-navigation/
  • W3C WAI, APG Menu and Menubar pattern: https://www.w3.org/WAI/ARIA/apg/patterns/menubar/
  • W3C WAI, APG Disclosure pattern: https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
  • W3C WAI, APG Disclosure Navigation Menu example: https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/
  • W3C WAI, APG Menu Button pattern: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/