An accessibly labeled button is a <button> (or element with role="button") that has a clear, descriptive accessible name— the text a screen reader announces when the user lands on it. A correctly labeled submit button is announced as "Submit application, button" rather than just "button." Without that name, screen-reader, voice-control, and AI-agent users cannot tell what the control does.
aria-label, aria-labelledby, visually hidden text.aria-label — they announce as "button" with no purpose.Think of a button's accessible name the way you think of the label on a light switch in a server room. Sighted users can sometimes guess from context, but anyone navigating in the dark — by screen reader, by voice, or by automated agent — needs the label to know whether they're flipping "Save" or "Delete account."
Every assistive-tech path through a page touches the button's accessible name. When that name is missing or vague, the failure mode is immediate and total:
WCAG and the WAI-ARIA Authoring Practices recognize four techniques for naming a button. Use them in this order of preference — visible text first, hidden text last.
<!-- 1. Visible text inside the button (best) -->
<button type="submit">Save changes</button>
<!-- 2. aria-label for icon-only buttons -->
<button type="button" aria-label="Close dialog">
<svg aria-hidden="true" focusable="false" width="16" height="16">
<path d="M2 2 L14 14 M14 2 L2 14" stroke="currentColor" />
</svg>
</button>
<!-- 3. aria-labelledby when the label exists elsewhere -->
<span id="cart-label">Add wireless headphones to cart</span>
<button type="button" aria-labelledby="cart-label">
<svg aria-hidden="true" focusable="false">...</svg>
</button>
<!-- 4. Visually hidden text inside the button -->
<button type="button">
<svg aria-hidden="true" focusable="false">...</svg>
<span class="sr-only">Search products</span>
</button>
<!-- The .sr-only utility class -->
<style>
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0,0,0,0);
white-space: nowrap; border: 0;
}
</style>Visible text is announced by every screen reader, indexed by search engines, parseable by AI agents, and targetable by voice control with no extra attributes. aria-label works for screen readers and voice control but is invisible to sighted users and to most search-engine text extraction. Reach for ARIA only when there is genuinely no room for visible text.
When a button uses an SVG or icon font, set aria-hidden="true" on the icon and (for SVG) focusable="false". Otherwise screen readers may announce the icon's tooltip plusthe button's accessible name, producing "trash icon, Delete photo, button."
Accessible-name failures are easy to introduce and easy to detect. A combination of automated and manual checks catches almost all of them.
button-name violations directly. Free and reliable for the "empty name" case.Generic verbs like "Submit" and "OK" technically pass automated audits but still fail users. Replace them with action + object: <button>Submit application</button>, <button>Save changes</button>, <button>Buy now</button>.
The most common WCAG failure on the web. Every icon-only button needs an aria-labelthat says what it does, not what it looks like. "Delete item" — not "Trash can."
<!-- Wrong: icon with no name -->
<button><svg>...</svg></button>
<!-- Right: aria-label describes the action -->
<button type="button" aria-label="Delete item">
<svg aria-hidden="true" focusable="false">...</svg>
</button>For buttons that switch a binary state (mute, follow, bookmark), keep the label stable and let aria-pressedcarry the state. Screen readers announce "Mute, toggle button, pressed" vs "not pressed." Don't flip the label between "Mute" and "Unmute" — that confuses users who rely on consistent landmarks.
<button type="button" aria-pressed="false" onclick="toggleMute(this)">
Mute
</button>For buttons that show or hide content (accordions, menus, "Read more"), use aria-expanded="true|false" and keep the label describing the target, not the state.
<button type="button" aria-expanded="false" aria-controls="details">
Show shipping details
</button>
<div id="details" hidden>...</div>The character "×" (multiplication sign) has no consistent accessible name across screen readers. Always pair it with an aria-label: <button aria-label="Close dialog">×</button>. Bonus: set aria-hidden="true"on the visible "×" if you also include hidden text, so it isn't double-announced.
When a button contains only an <img>, the image's alt attribute becomes the button's accessible name. An empty alt="" means an unlabeled button.
<!-- Wrong: empty alt = no name -->
<button><img src="/print.png" alt=""></button>
<!-- Right: alt describes the action -->
<button><img src="/print.png" alt="Print invoice"></button>When the button has both, hide the icon from assistive tech (aria-hidden="true") and let the text serve as the name. Don't add a redundant aria-label— if you do, it overrides the visible text and creates the "label mismatch" problem (visible says "Save", screen reader says "Submit form"), which fails WCAG SC 2.5.3 Label in Name.
<div onclick> styled like a button has no role, no keyboard support, no name, and no focus ring. If you absolutely must build a custom control, you need role="button", tabindex="0", keyboard handlers for Enter and Space, and an accessible name. Or just use <button>and reset its styles — it's 95% less work.
What's happening: The button contains only an SVG, icon font, or image with empty alt. Screen readers announce just "button." Voice control users cannot target it.
Fix: Add aria-label="Action description" to the button and aria-hidden="true" to the icon. The label should describe the action ("Delete item"), not the icon ("Trash can").
What's happening: The button passes automated audits because it has a name, but the name carries no information out of context.
Fix: Use action + object. Replace "Submit" with "Submit application," "Click here" with "Download pricing PDF," "OK" with "Confirm deletion."
What's happening: The button shows "Save" but aria-label="Submit form" overrides it. Voice-control users say "click Save" and nothing happens — the button's accessible name is "Submit form." This fails WCAG SC 2.5.3 Label in Name.
Fix: Remove the aria-label. The visible text is already the accessible name. Only override visible text if the visible text is genuinely insufficient (rare).
What's happening: The element looks like a button but has no role, no keyboard activation, no focus ring, and no accessible name.
Fix: Replace it with <button type="button">. If a refactor is too disruptive, add role="button", tabindex="0", an accessible name, and keyboard handlers for Enter and Space — but the native element is almost always less code.
All three give a button an accessible name, but they differ in how they affect sighted users, search engines, and AI extraction. Pick the technique that matches the design constraint.
| Technique | Visible to Sighted Users | Read by Screen Readers | Indexed for SEO/AI | Best For |
|---|---|---|---|---|
Visible text inside <button> | Yes | Yes | Yes | Default — use whenever there is room for a label. |
aria-label | No | Yes | Limited (some crawlers ignore it) | Icon-only buttons, close (×) buttons. |
aria-labelledby | No (but references visible text) | Yes | Limited | When the label already exists elsewhere on the page (e.g. card titles). |
Visually hidden <span> | No | Yes | Yes (it's real DOM text) | Icon buttons where you also want the name in the DOM for indexing. |
The primary criterion is WCAG 2.2 SC 4.1.2 Name, Role, Value (Level A), which requires every interactive control to have a programmatically determinable name and role. SC 2.4.4 Link Purpose (Level A) applies when buttons act like links, and SC 2.5.3 Label in Name (Level A) applies when visible text and the accessible name disagree. Failing any of these is a Level A violation — the strictest tier.
Visible text. It is announced by every screen reader, indexed by search engines, parseable by AI agents, targetable by voice control without extra attributes, and useful to sighted users. aria-labelis appropriate only when visible text genuinely won't fit — the canonical case is an icon-only button.
Almost always because the button has no accessible name. The <svg> or icon font inside it is not text, and without an aria-label, hidden text, or alt attribute on a contained image, the computed accessible name is empty. Add aria-label="Action description" to the button and aria-hidden="true" to the icon and re-run the scan.
Use aria-pressed. Keeping a stable label ("Mute") and toggling aria-pressed="true|false"means screen readers announce the state explicitly ("Mute, toggle button, pressed") and sighted users always see the same control in the same place. Swapping text between "Mute" and "Unmute" on every click moves the visual landmark and confuses keyboard users.
Inconsistently. The multiplication sign (×) is announced as "multiplication" by some screen readers, "times" by others, and skipped by some. Always pair it with aria-label="Close dialog" (or similar) on the button. If you want the visible × to remain decorative, set aria-hidden="true" on it and let the aria-label carry the name.
Yes. Agentic AI systems and rendering pipelines parse button text to understand page intent and (for agents) to plan actions. A button with no accessible name cannot be invoked by an AI agent and contributes no semantic signal to indexing. Specific, descriptive button text helps both human users and AI systems understand what your CTAs do.
SEO: descriptive button text contributes to topical relevance signals and feeds in-page interaction crawls. "Download pricing PDF" tells Google what the page is about; "Click here" tells it nothing. Conversion: A/B tests consistently show specific CTAs ("Start free trial", "Buy now") outperform generic ones ("Submit", "Continue") — clear button copy is one of the highest-ROI accessibility improvements you can ship.
Button labeling is the lowest-effort, highest-impact accessibility fix on most sites. Visible text wherever possible; aria-label for icon-only buttons; aria-pressed for toggles; aria-expanded for disclosures; never <div onclick>. Get those five right and you've eliminated the most common WCAG Level A failure on the web — the same one that appears on roughly 28% of home pages tested.
Run a Greadme deep scan to see exactly which buttons on your site have empty or generic accessible names, then fix them in order of impact.