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 witharia-expandedcarrying 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 anaria-expandedreports the state twice. - A state, if you took the first route.
aria-expandedset 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.
- 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.
- 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
- 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.
- Tab into the navigation and open every submenu without touching the mouse.
- Press Escape in an open submenu. Does it close, and does focus come back to the trigger?
- 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.