Your Hamburger Menu Is Called "Button"
A button with no accessible name is announced as one word: "button". Unlike a link, there is no URL to fall back on and read out, so the user cannot guess, cannot preview, and the only way to find out what it does is to press it. And the buttons this happens to are the ones every modern page depends on.
The Five Buttons That Break on Almost Every Site
Look at the header of nearly any website built in the last five years and you will find the same set of controls, each drawn with an icon and no words: the hamburger that opens the menu, the magnifier that opens search, the cart, the close X on the cookie banner and the modal, and the play and pause controls on a video or carousel. Add the carousel arrows and the bin icon on a basket row.
Every one of those looks correct on screen. Every one of them, if the developer did not add a name, is read to a screen-reader user as "button", and to a voice-control user it does not exist at all, because there is nothing to say to activate it. The icon-only pattern is why empty buttons sit alongside missing alt text and missing form labels among the most common failures automated testing finds.
How a Button Gets Its Name
Browsers compute an accessible name for every button in a fixed order, and the first source that produces text wins:
aria-labelledby, pointing at the id of another element whose text becomes the name.aria-label, a string on the button itself.- The button's own content: its text, including visually hidden text, plus the alt text of any image and the title of any inline SVG inside it.
- For
inputbuttons, thevalueattribute. A submit or reset input with no value gets the browser's default "Submit" or "Reset", which is a real name. - The
titleattribute, as a last resort. It works, but it is inconsistently announced and invisible to touch and keyboard users, so do not rely on it.
An SVG with no title and no text next to it contributes nothing. A Font Awesome i element contributes nothing. An img with an empty alt contributes nothing. That is how a button with visible content can still have an empty name.
What WCAG Requires
| Criterion | Level | What fails |
|---|---|---|
| 4.1.2 Name, Role, Value | A | A button with no accessible name. Also a clickable div or span with no button role, because it has no role to announce. |
| 1.1.1 Non-text Content | A | An image button, input type="image", with no alt text. |
| 2.5.3 Label in Name | A | A button whose visible label is not contained in its accessible name, for example visible "Add to cart" with aria-label="atc-btn". Voice-control users say what they see, and nothing happens. |
| 2.1.1 Keyboard | A | A div with role="button" and no tabindex cannot be reached by keyboard at all, and needs Enter and Space handling added by hand. |
Two things people often count as failures are not. A repeated name, "Add to cart" on twelve product cards, meets 4.1.2, which asks for a name, not a unique one; it is worth disambiguating for the buttons list, but it is not a violation. And a single form's submit button called "Submit" is fine. Five of them on one page is the problem.
How to Fix Each Pattern
For icon-only buttons, name the button and hide the icon. The name should say what the button does, not what the icon looks like: "Open menu", not "hamburger"; "Close", not "X".
<!-- Announced as "button": fails 4.1.2 -->
<button class="menu-toggle"><svg class="icon-menu"></svg></button>
<!-- Fixed with aria-label -->
<button class="menu-toggle" aria-label="Open menu" aria-expanded="false">
<svg aria-hidden="true" class="icon-menu"></svg>
</button>
<!-- Fixed with visually hidden text (also survives translation) -->
<button class="search-toggle">
<svg aria-hidden="true" class="icon-search"></svg>
<span class="sr-only">Search</span>
</button>
<!-- Image button: needs alt, fails 1.1.1 without it -->
<input type="image" src="/go.png" alt="Search">For buttons with visible text, do not add an aria-label at all unless you have to. The visible text is already the name. If you need more context, extend it with visually hidden text so the visible words stay inside the name and 2.5.3 still passes: "Remove<span class=\"sr-only\"> Blue T-shirt from basket</span>".
For a clickable div, replace it with a real button. You get focus, Enter and Space, and the role for free. Adding role="button" and tabindex="0" and key handlers by hand is possible, but every hand-built button is one more place to get it wrong.
For toggles, the name stays the same and the state changes. A menu button is "Menu" with aria-expanded true or false, and a play button can be "Play" with aria-pressed, rather than swapping labels that a screen reader may not announce.
How to Check Your Own Page
The checker at the top of this article reads your page's served HTML and lists every button a real person cannot identify: no accessible name, an image button with no alt, a visible label missing from the name. Those are counted as failures. Clickable elements with no button role, role-buttons that cannot take focus and generic names like "Submit" repeated across the page are listed for review, because a framework can add attributes after the page loads and a single "Submit" is fine.
That limit is also why one page is not the answer. Buttons are the most component-driven element on a page, and many frameworks add them in the browser after load. The whole-site scan renders every page in a real browser, so it sees the buttons JavaScript builds, and names every WCAG 2.1 AA failure by criterion with the element and page it is on. The checker's result hands your domain straight to it.
Frequently Asked Questions
Why does my screen reader just say 'button'?
Because the button has no accessible name. The browser looks for a name in aria-labelledby, then aria-label, then the button's text content, including image alt text and SVG titles, then the title attribute. An icon button that contains only an SVG or icon font with none of those produces an empty name, and the screen reader announces the role alone. Add an aria-label or visually hidden text that says what the button does.
Is aria-label or visually hidden text better for icon buttons?
Both give the button a valid accessible name and both meet WCAG 4.1.2. Visually hidden text has two advantages: browser translation tools translate it, while many skip aria-label, and it stays in sync if someone later adds visible text. Aria-label is simpler to add to an existing component. Either way, mark the icon itself aria-hidden so it is not announced as well.
Does every button need a unique name?
No. WCAG 4.1.2 requires a name, not a unique one, so twelve 'Add to cart' buttons on a product grid do not fail. They are harder to use from a screen reader's buttons list, where the entries are indistinguishable, so adding the product name in visually hidden text is a good improvement. It is a usability fix rather than a compliance one.
What is SC 2.5.3 Label in Name?
A level A criterion that says a control's accessible name must contain its visible label. It exists for voice-control users, who activate controls by saying the words they see. A button showing 'Add to cart' but named 'atc-button' through aria-label cannot be activated by saying 'click Add to cart'. The fix is to drop the aria-label, or make it start with the visible words.
Can I use a div with an onclick handler as a button?
It fails accessibility unless you rebuild everything a button gives you: role='button' so it is announced as one, tabindex='0' so it can be reached by keyboard, handlers for both Enter and Space, and an accessible name. A native button element provides all of that automatically, so replacing the div is almost always the better fix.
Do submit buttons with no value have an accessible name?
Yes. An input of type submit or reset with no value attribute gets the browser's built-in default, 'Submit' or 'Reset', as its accessible name, so it does not fail 4.1.2. An input of type button with no value genuinely has no name and does fail. A button element with no content has no default and also fails.
Five Icons, One Component Library
Unnamed buttons almost never come one at a time. They come from the icon-button component the whole site shares, so the header you check on one page is the header on every page. Check it with the tool at the top of this article.
Then scan the whole site. Every page rendered in a real browser, every WCAG 2.1 AA failure named by criterion with the element it is on. No signup, no card.