Skip to main content
WCAGrules
Quick navigation

Guides · Interface patterns

Navigation Menus, Built So Everyone Can Get Past Them

Navigation is the first thing on the page and the first thing keyboard users have to survive.

Last reviewed August 31, 2026

Everybody meets your navigation first. A mouse user sees a row of links. A keyboard user meets a gauntlet they have to cross on every single page. A screen reader user hears a wall of announcements before the page has said anything.

Most navigation fails in one of three ways. It cannot be opened without a mouse, it cannot be skipped, or it never says which page you are on. All three are cheap to fix and none of them is visible from a screenshot.

Hover Is Not an Interaction

A submenu that opens on hover and nothing else is unreachable by keyboard, and it is unreliable on touch, because phones fake a hover on the first tap and often send the visitor to a category page they never wanted.

Make the trigger a real control. If the top-level item goes nowhere on its own, it is a button rather than a link, and a button announces itself as something that acts, carries aria-expanded, and answers Enter and Space for free. Where the item both links somewhere and opens a submenu, which is the mega-menu case, split it. A link for the destination, and a separate adjacent button for the submenu, so each control does one thing. W3C publishes a worked navigation example of exactly that shape, and it is worth building from rather than inventing.

Two more hover details are worth having. Submenus should not open when somebody is tabbing through the menu, because then a keyboard user has to step through every submenu item to reach the next top-level one. And where a submenu does open on hover, add a short delay before it closes when the pointer leaves, because a pointer that is not perfectly steady loses the menu on the way to it. W3C's own example uses a one-second timer. Any submenu that opens on hover also owes the content on hover rule its three conditions, and the hoverable one is why that delay is not merely polite.

The rule that catches this

A hover-only submenu fails 2.1.1 Keyboard, always. Whether it also fails 4.1.2 Name, Role, Value depends on what the trigger is. A div or a span with a hover handler has no role saying it opens anything, so it fails both. An <a> or a <button> carrying aria-expanded announces correctly and simply cannot be operated, so that one is 2.1.1 alone. Naming the right one keeps the finding standing.

Build the Menu as a List

This is the cheapest thing on the page and it gets left out constantly. Put the menu items in a ul, one item per li. The structure is what lets a screen reader announce how many items there are and move between them with list commands, so a visitor hears eight items rather than counting as they go. Use an unordered list, because website navigation is not in a meaningful order. Save the ordered list for something like a breadcrumb trail where the sequence is the meaning.

The Hamburger Owes You Three Things

  • A name. Visually hidden text or an aria-label. Two conventions work and they must not be combined. Either a fixed name such as Menu with aria-expanded carrying the state, or a hidden label that changes with the state, such as show submenu and hide submenu, ideally naming the parent item as in show Space Bears submenu. Pick one. A label that changes and an aria-expanded reports the state twice.
  • A state, if you took the first route. aria-expanded set to true or false on the button, updated every time the panel opens or closes. A state that is right on load and wrong afterwards is worse than none.
  • A way back. Escape closes the panel and returns focus to the button that opened it, the same contract a dialog owes.

Give People a Way Past It

Fifty navigation links is fifty tab stops on every page, and 2.4.1 Bypass Blocks exists for that. Here is the part worth money before anybody writes a ticket. W3C gives two sufficient routes, not one, and you may already have satisfied it without knowing. Either add a link that skips the repeated block, or group the repeated block so it can be skipped.

  1. The skipping route. A link at the top of each page that goes straight to the main content, the one everybody thinks of. Or a link at the beginning of a repeated block that jumps to the end of it. Or links at the top of the page to each area of the content.
  2. The grouping route. ARIA landmarks marking the regions of the page. Or a real heading element at the start of every section, which is sufficient on its own, so a site with proper heading structure has already conformed with no skip link anywhere. Or an expandable and collapsible menu that lets somebody step over the block, which is what a hamburger already is, one step away.

So check what you have before you commission what you do not. And know that the routes serve different people. Landmarks and headings serve screen reader users, who navigate by them. A skip link serves the sighted keyboard user, who has neither.

Skip links themselves fail more often than they work, and the cause is almost always the same. display: none and visibility: hidden both take an element out of the tab order and out of the accessibility tree, and they do it whether they are on the element itself or on any ancestor of it, so a hidden wrapper is enough to break the link inside it. Hide the link by position instead and reveal it on focus. That version is fine, and the standard says so directly. A skip link is not required to be visible when it does not have focus, and content revealed on keyboard focus is expressly outside the content-on-hover rule, so it owes none of those three conditions.

Say Where the User Is

A bolded link is a visual signal and nothing else. W3C gives two ways to mark the current page, and presents them in an order most people have backwards. The first is to remove the anchor, so the current item is plain text nobody can click, which avoids the confusion of a link that goes where you already are. The second is aria-current="page", and W3C describes that one as particularly useful when the anchor cannot be removed.

Either way, only one element in the set gets marked as current. And the technique W3C publishes for navigation bars wants both signals together, a visual style somebody can see and a programmatic marker somebody can hear, so the attribute is an addition to the bold rather than a replacement for it.

Name Your Landmarks, But Only Where It Helps

Wrap the menu in a nav element, which makes it a navigation landmark a screen reader user can jump to. The labeling rule is more precise than always label everything. Where a page has more than one navigation landmark, each needs its own unique label. Where a page has only one, a label may not be needed at all. Almost every real page has a main menu and a footer menu or a breadcrumb, so you are in the first case.

There is one deliberate exception. Where two landmarks hold an identical set of links, such as pagination repeated above and below a table, give them the same label rather than inventing two, because the extra distinguishing words are more distracting than helpful.

Mega Menus

A mega menu is a panel of ordinary links, not an application menu, and the two are different things in ARIA. Resist role="menu", and it helps to know exactly what you would be promising. The menu role commits you to arrow key navigation between items, Home and End, typeahead by first letter, Escape closing the menu and returning focus, and every item but one removed from the page tab sequence, because a menu is meant to behave like the menu bar at the top of a desktop application.

This is a cost judgement rather than an impossibility. W3C publishes a working menubar navigation example, so it can be done and it is a large amount of code. A button controlling a region of plain links behaves better on a website and needs a fraction of it. If you already shipped role="menu", the list above is what to test yourself against.

Keep the tab order matching the visual order. A panel appended to the end of the document and positioned into place with CSS sends focus somewhere unexpected, which is a 2.4.3 Focus Order failure, and the fix has a name. Insert the panel into the document immediately after its trigger.

How to Test It in Two Minutes

  1. Tab from the very top of the page. Does a skip link appear, or does the page have landmarks and section headings instead? Any of those counts.
  2. Tab into the navigation and open every submenu without touching the mouse.
  3. Press Escape in an open submenu. Does it close, and does focus come back to the trigger?
  4. Shrink to a phone width, open the hamburger, and repeat the whole sequence. The pass condition is not just that it works. The items that show should appear in the same order, with the same wording and the same destinations as the desktop version, which is also what 3.2.3 Consistent Navigation asks for.

Common questions

Should navigation items be links or buttons?
A link if it goes to a page. A button if it opens a submenu and goes nowhere itself. Where one item does both, which is the mega-menu case, use a link for the destination and a separate adjacent button for the submenu, so each control does one job. W3C's own menus tutorial shows a third route, an anchor carrying aria-expanded, which is valid. We prefer the button, because the ARIA specification advises the button role for anything that triggers an action without changing page location.
Do we actually need a skip link?
Maybe not. W3C gives the bypass rule two sufficient routes, and the second one is simply grouping the repeated block so it can be skipped, which a real heading element at the start of each section already does. A site with proper heading structure already conforms. A skip link is still worth having, because it serves the sighted keyboard user who does not navigate by headings or landmarks.
Does a skip link have to be visible?
No. The rule does not require it to be visible when it does not have focus, so hiding it by position and revealing it on focus is fine. What breaks it is display: none or visibility: hidden, on the link or on any ancestor, because both remove it from the tab order entirely. A skip link revealed on focus is also outside the content-on-hover rule, so it owes none of those conditions.
How should I mark the current page in a menu?
Two ways, and W3C presents removing the link first. Take the anchor off the current item so it is plain text, or keep the link and add aria-current="page", which is the answer when the markup will not let you remove it. Only one item in the set gets marked. Keep the visual styling as well, because the recommended technique for navigation bars wants both a visible signal and a programmatic one.
Can I use role=menu for a mega menu?
You can, and you are promising a lot. The role commits you to arrow keys between items, Home and End, typeahead by first letter, Escape closing and returning focus, and every item but one removed from the tab sequence. W3C does publish a working menubar navigation example, so it is possible. A button controlling a panel of ordinary links behaves better on a website and costs a fraction as much.

Sources

Keep reading

More on interface patterns

Reading about it is the cheap part.

Find out where your site actually stands. The free scan checks 10 pages in a real browser against all 90 supported automated rules, keeps its 27 best-practice checks separate from WCAG findings, and names the rule behind every finding. The full audit adds an expert review and a real blind screen-reader user. From $499, with the report in 5 business days on Rapid and 10 on Standard, and the clock starting at cleared payment.

Go somewhere useful

Find tools, resources and your workspace.

29 destinations