Quick Fix · ARIA · Life Hacks

The Hamburger Menu Confession: Why Your Icon-Only Buttons Are Probably Nameless to Screen Readers

Illustration of a hamburger-menu icon button with an empty speech bubble containing only a question mark above it, in an oxblood and cream editorial style
  • Quick Fix
  • ARIA
  • Life Hacks

Here's a two-minute confession exercise. Turn on VoiceOver (Cmd+F5 on a Mac) or fire up NVDA on Windows, tab your way to your own site's hamburger menu, and just... listen. If what you hear is a flat, contextless "button" — no "menu," no "open," no anything — you've just found a bug that's been shipping to production for who knows how long.

The good news: this is one of the fastest fixes in accessibility. No redesign, no new component library, no meetings. Just one attribute, added to the handful of icon buttons every site seems to collect — hamburger menus, search icons, close buttons, cart icons — and the "button" mystery disappears.

The Stat: WCAG 4.1.2 Name, Role, Value (Level A) requires every UI component to have a programmatically determinable accessible name. (Source: W3C Web Content Accessibility Guidelines)

Before and after: labeling an icon-only hamburger button Two code panels. The left panel shows a button containing only an SVG icon with no label, announced by screen readers as "button". The right panel shows the same button with an aria-label attribute added, announced as "Open menu, button". BEFORE <button> <svg>...</svg> </button> announced as: "button" ? AFTER <button aria-label="Open menu"> <svg>...</svg> </button> announced as: "Open menu, button"

Why "button" happens in the first place

Icon buttons are visually self-explanatory — three horizontal lines obviously mean "menu," a magnifying glass obviously means "search." Obvious to sighted users, that is. A screen reader doesn't see the icon, doesn't infer meaning from shape or convention, and doesn't know that your design system has used the same hamburger glyph on forty other sites. It reads the DOM, and the DOM is where the meaning either exists or doesn't.

This is a really common trap because the bug is entirely invisible in a visual review. Open the page, look at the button, and it's obviously a menu toggle — three lines, top-right corner, exactly where every user expects it. A designer signs off. A developer ships it. A QA pass checks that clicking it opens the menu, confirms it does, and moves on. Every single person in that chain saw a working, sensible button. Nobody heard it.

When your markup looks like this:

<button>
  <svg>...</svg>
</button>

There's no text node inside the button for the accessible name computation to pick up. The <svg> icon isn't exposed as text (and even if it had a <title>, browser support for that being picked up as the accessible name is inconsistent). So assistive tech falls back to announcing the element's role only: "button." Not "menu, button." Not "open navigation, button." Just "button" — a locked door with no sign on it.

This is exactly what WCAG 4.1.2 Name, Role, Value exists to catch: every interactive UI component needs a programmatically determinable name, not just a visual cue. A button with no accessible name fails this criterion outright, regardless of how clear the icon looks to you. It doesn't matter that the icon is a universally recognized convention, that it's positioned exactly where a user would expect it, or that a sighted user would never be confused for a second — the success criterion is about what's exposed in the accessibility tree, not what's visually implied.

It's worth being precise about what "accessible name" actually means here, because it trips people up. The accessible name is not the same thing as the element's role (which is "button," computed automatically from the <button> tag) and it's not the same thing as its value or state (like whether a toggle is pressed). The name is specifically the label — the string that answers "what is this called." Role, name, and value are three separate pieces of information a screen reader announces together, and an icon-only button typically has two out of three: role ("button") and, for a toggle, maybe a state — but no name. That's the gap aria-label closes.

The one-line fix

The most direct fix is aria-label, a real, standard, spec-defined ARIA attribute built for exactly this situation — providing an accessible name when there's no visible text label to point to. Check MDN's aria-label documentation for the full spec, but the practical use is this simple:

<button aria-label="Open menu">
  <svg>...</svg>
</button>

That's it. One attribute, one string, and your hamburger button now has a real name. A screen reader user tabbing to it hears "Open menu, button" instead of a mystery. Same fix pattern applies to every icon-only control on your site:

Notice what didn't change: the icon itself, the visual design, the CSS, the click handler, the component's behavior. aria-label is invisible to sighted users — it adds nothing to the rendered page and changes nothing about how the button looks or behaves for anyone not using assistive technology. That's part of why this fix is so low-risk to ship: there's no design review needed, no risk of breaking a visual layout, and no browser compatibility concern worth worrying about. aria-label has been supported everywhere that matters for well over a decade.

Icon Bad announcement aria-label value
Hamburger menu (☰) "button" aria-label="Open menu"
Search (🔍) "button" aria-label="Search"
Close (✕) "button" aria-label="Close"
Cart "button" aria-label="Shopping cart"
Play "button" aria-label="Play video"

A few things worth getting right while you're in there:

  • Label the action, not the icon — aria-label="Open menu", not aria-label="Hamburger icon".
  • Update the label dynamically if the button toggles state — a menu-open button that becomes a close button should switch to aria-label="Close menu" when expanded.
  • Don't put aria-label on the inner <svg> — put it on the <button> itself, since that's the element that receives focus and gets announced.
  • If the icon is purely decorative and the button also has visible text, skip aria-label and instead hide the icon from assistive tech with aria-hidden="true" so the visible text alone provides the name.
  • Re-test with an actual screen reader after the change — don't just trust that the attribute is there.

For the broader pattern — expected keyboard behavior, focus handling, and how buttons should behave once they're properly named — the ARIA Authoring Practices Guide's button pattern is the reference to bookmark. And if you want more worked examples of aria-label and its siblings (aria-labelledby, aria-describedby) in real UI contexts, WebAIM's guide to ARIA techniques is a solid, practical companion.

Three mistakes people make once they know about aria-label

Once a team learns about this fix, the natural instinct is to sprinkle aria-label everywhere as a reflex. That overcorrection creates its own problems, so it's worth calling out the three most common ones.

Labeling elements that already have visible text. If a button already reads "Subscribe" on screen, it doesn't need aria-label="Subscribe" bolted on top. Worse, if the aria-label text doesn't exactly match the visible text, you create a mismatch between what sighted users read and what voice-control or screen-reader users hear — which is its own usability failure. Reserve aria-label for cases where there is genuinely no visible text to reference.

Writing labels that describe the icon instead of the action. aria-label="Hamburger icon" technically gives the button a name, so an automated accessibility scanner will stop flagging it — but it tells a screen reader user nothing useful about what will happen if they activate it. The label should describe the outcome of pressing the button ("Open menu," "Close dialog," "Add to cart"), not the appearance of the icon sitting inside it. This is a case where passing an automated check and actually being usable are two different bars, and only one of them matters to a real person.

Forgetting the label needs to update with state. A hamburger button that expands into a visible navigation panel is often the same DOM element throughout — it doesn't get swapped for a different button when it opens. If your aria-label stays frozen at "Open menu" after the menu is already open, a screen reader user has no way to know the state changed. Pair the label update with aria-expanded="true"/false on the same button so both the name and the state stay accurate as the UI changes.

Why this is worth doing today, not "eventually"

Unlabeled icon buttons are one of the most common accessibility bugs on the web, precisely because they're invisible to the people who ship them. Nobody catches it in a visual QA pass — the button looks fine. It only shows up when someone actually uses a screen reader, and by then it's often a support ticket, a complaint, or a line item in a legal demand letter rather than a two-line code review comment.

The fix doesn't require a design sign-off, a sprint, or a new dependency. It requires opening your components file, finding every <button> that wraps only an icon, and giving it a name. Most sites have five to ten of these total — menu toggles, modal close buttons, carousel arrows, social icons, filter toggles. An afternoon, realistically.

There's also a quieter cost to leaving these unlabeled that has nothing to do with legal exposure. Every "button" announcement is a small moment of friction for a screen reader user — a pause to guess, a backward tab to find surrounding context, sometimes a decision to just leave the page rather than gamble on what a mystery button will do. Multiply that by every icon-only control across every page of a site, and a few missing aria-label attributes quietly become a genuinely worse experience for anyone navigating by ear rather than by eye. None of that shows up in a bounce-rate dashboard as "accessibility issue" — it just shows up as people leaving.

If you did the exercise at the top of this article and heard "button" where you expected "Open menu," you already know exactly where to start: your own site, right now, with a text editor open. Fix the hamburger menu first since it's on every page, then work through search, close, and cart icons. Each one is the same one-line change, and each one is a small, permanent improvement that ships the moment you commit it.

If you're not sure how many nameless buttons are hiding in your codebase, that's exactly the kind of thing worth checking systematically rather than button-by-button — you can find every unlabeled button on your site with a free scan and get a concrete list instead of a guess.