Navigation menu (disclosure)

A navigation menu is the set of links that move people between the sections of a site. Most of it is just a list of <a> links inside a <nav> landmark — and where one section has sub-pages, a single top-level item expands to disclose them. The expanding item is a real <button> with aria-expanded that shows or hides a normal <ul> of links. That is the disclosure pattern, and it is the right choice for site navigation.

Prefer native HTML wherever it covers the need: a plain list of links needs no JavaScript at all, and the dropdown here is just a button toggling a list. The page is a reference implementation — the markup is authored statically and nav-menu.js enhances it, so with scripting off the submenu is simply visible and every link stays reachable.

Live demo

Tab through the links and the Products toggle. Press Enter or Space on the toggle to open or close its submenu, then Esc to close it and return focus to the toggle. Clicking or tabbing away also closes it.

When to use

Reach for this when you have site navigation

  • Use it for a website main menu — a set of links to pages or sections, where one or more items expand to reveal sub-pages.
  • If no item needs to expand, you don’t need this at all: a <nav> with a <ul> of links is complete and needs no JavaScript.
  • Only the expanding item becomes a disclosure: a <button> with aria-expanded toggling a normal list of links.
  • Do not use role="menu" / role="menubar" here — those are for application command menus and impose a roving-focus arrow-key model people don’t expect on a website.

Markup

Author the <nav> landmark, the link list, and the one disclosure item with its ARIA wiring already in place, then let nav-menu.js manage the open/closed state. With scripting off, the submenu is shown and every link works.

nav-menu.html
<nav class="ewa-navmenu" aria-label="Main" data-navmenu>
  <ul class="ewa-navmenu__list">
    <li><a class="ewa-navmenu__link" href="/">Home</a></li>
    <li><a class="ewa-navmenu__link" href="/about/">About</a></li>
    <li>
      <button class="ewa-navmenu__toggle" type="button"
              aria-expanded="false" aria-controls="nm-products">Products</button>
      <ul id="nm-products" class="ewa-navmenu__submenu" hidden>
        <li><a href="/products/scanner/">Site scanner</a></li>
        <li><a href="/products/monitoring/">Monitoring</a></li>
        <!-- …more links… -->
      </ul>
    </li>
    <li><a class="ewa-navmenu__link" href="/pricing/">Pricing</a></li>
  </ul>
</nav>

Keyboard interactions

Because this is the disclosure pattern, the keyboard model is the everyday one: links and the toggle are in the normal Tab order, and the toggle behaves like any button. No arrow-key roving is imposed.

Keyboard interactions
Key Action
Tab / Shift + Tab Moves through the links and the toggle in source order, like any links and buttons.
Enter or Space On the toggle, opens its submenu if closed or closes it if open (flips aria-expanded).
Esc When a submenu is open, closes it and returns focus to its toggle.
Tab out Moving focus out of the menu closes any open submenu.
Enter On a submenu link, follows the link — it is an ordinary <a>.

ARIA roles, states, and properties

There is almost no ARIA to get wrong here — that is the point of the disclosure pattern. A <nav> landmark, a native <button>, and the [hidden] attribute do nearly all the work.

ARIA attributes and where they live
Attribute On Purpose
<nav> landmark The menu wrapper Exposes the navigation region so AT can list and jump to it.
aria-label <nav> Names the landmark (e.g. “Main”) so it’s distinct from other nav regions.
<button> The disclosure control A native button: focusable, operable with Enter/Space, announced as a button.
aria-expanded The toggle button true while its submenu is shown, false when hidden.
aria-controls The toggle button Points at the submenu’s id so AT knows what the button discloses.
hidden The submenu <ul> Closes the submenu — it is a normal list, shown or hidden, with no menu roles.

Common mistakes

Pitfalls to avoid

  • Using role="menu" / role="menubar" for site navigation. Those are for application command menus and impose a strict arrow-key, roving-focus keyboard model users do not expect on a website. For site navigation use this disclosure pattern — a <nav> of links with a button-driven dropdown.
  • Building the toggle from a <div> or <a>. Use a real <button> so it is focusable and operable with Enter and Space for free.
  • Forgetting aria-expanded. Without it, screen-reader users can’t tell whether the submenu is open or closed.
  • Signalling open state with colour alone. Pair the state with a rotating caret (and aria-expanded) so it survives mono and forced-colours themes.
  • Breaking it with JS off. Author the submenu so that, without scripting, it stays visible and its links remain reachable.