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>witharia-expandedtoggling 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 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.
| 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.
| 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.