Back to All Rules
Critical4.1.2 Name, Role, Value (Level A)Rule ID: button-name

Buttons must have discernible text

A button element does not have text content or accessible text. Screen reader users cannot understand the button's purpose.

Rule Description

This rule checks that all <button> elements, elements with type="button", type="submit", or type="reset", and elements with role="button" have accessible names.

What This Rule Checks

  • Text content inside the button
  • aria-label attribute
  • aria-labelledby attribute
  • alt attribute on images inside buttons
  • title attribute (as last resort)

What This Rule Does Not Check

  • Quality or clarity of button text
  • Whether button text matches visual design
  • Button functionality or behavior

Best Practices

  1. Prefer visible text - Use text content when possible
  2. Be specific - "Delete Comment" not "Delete" or "OK"
  3. Be concise - Keep labels short but descriptive
  4. Match visual text - aria-label should match what sighted users see
  5. Hide decorative icons - Use aria-hidden="true" on icon elements

Why It Matters

Impact on Users

  • Screen reader users hear "button" without knowing what it does
  • Voice control users cannot activate the button by name
  • Users with cognitive disabilities need clear button labels to understand functionality
  • All users benefit from clear, descriptive button text

Real-World Scenario

A screen reader user encounters a form with a search field and a button. The screen reader announces: "Edit text, search. Button." Without button text, they don't know if it's a submit button, clear button, or filter button.

How to Fix

Solution 1: Use Text Content

Add descriptive text inside the button element.

Bad Example:

<!-- Empty button --> <button></button> <button type="submit"></button>

Good Example:

<button type="submit">Search</button> <button type="button">Clear Form</button> <button>Add to Cart</button>

Solution 2: Icon Buttons with aria-label

For icon-only buttons, use aria-label to provide text.

Bad Example:

<!-- Icon without label --> <button class="icon-btn"> <i class="fa fa-trash"></i> </button>

Good Example:

<button class="icon-btn" aria-label="Delete item"> <i class="fa fa-trash" aria-hidden="true"></i> </button> <button aria-label="Close dialog"> <svg aria-hidden="true">...</svg> </button> <button aria-label="Settings"> <span class="material-icons" aria-hidden="true">settings</span> </button>

Solution 3: Image Buttons with Alt Text

When using images inside buttons, ensure proper alt text.

Bad Example:

<!-- Image without alt --> <button> <img src="search.png"> </button>

Good Example:

<button> <img src="search.png" alt="Search"> </button> <!-- Better: Use aria-label for consistency --> <button aria-label="Search"> <img src="search.png" alt="" aria-hidden="true"> </button>

Solution 4: Icon + Text with Tooltip

Combine visible text with icons for clarity.

Good Example:

<button> <i class="fa fa-save" aria-hidden="true"></i> Save </button> <button> <svg aria-hidden="true">...</svg> <span>Download PDF</span> </button> <!-- Icon with visually hidden text --> <button> <i class="fa fa-trash" aria-hidden="true"></i> <span class="visually-hidden">Delete</span> </button>

Solution 5: Using aria-labelledby

Reference existing text on the page.

Good Example:

<h2 id="modal-title">Confirm Deletion</h2> <p>Are you sure you want to delete this item?</p> <button aria-labelledby="modal-title confirm-btn"> <span id="confirm-btn">Confirm</span> </button> <!-- Better approach --> <button>Confirm Deletion</button>

Common Mistakes

1. Empty Button

<!-- FAIL --> <button></button> <button type="submit"></button> <button class="btn-primary"></button>

2. Icon Without Label

<!-- FAIL --> <button> <i class="icon-close"></i> </button> <!-- PASS --> <button aria-label="Close"> <i class="icon-close" aria-hidden="true"></i> </button>

3. Image Without Alt

<!-- FAIL --> <button> <img src="icons/print.png"> </button> <!-- PASS --> <button> <img src="icons/print.png" alt="Print"> </button>

4. Generic Text

<!-- FAIL - Too vague --> <button>Click Here</button> <button>Submit</button> <button>OK</button> <!-- PASS - Specific --> <button>Search Products</button> <button>Submit Application</button> <button>Confirm Purchase</button>

5. Whitespace Only

<!-- FAIL --> <button> </button> <button>&nbsp;</button>

6. Relying on Title Alone

<!-- FAIL - title is not sufficient --> <button title="Save"> <i class="icon-save"></i> </button> <!-- PASS --> <button aria-label="Save"> <i class="icon-save" aria-hidden="true"></i> </button>

Examples by Button Type

Submit Buttons

<!-- Forms --> <form> <input type="text" id="search" name="q"> <button type="submit">Search</button> </form> <!-- Login --> <button type="submit">Sign In</button> <button type="submit">Create Account</button> <!-- Checkout --> <button type="submit">Complete Purchase</button>

Action Buttons

<!-- CRUD operations --> <button type="button">Add User</button> <button type="button" aria-label="Edit profile"> <i class="fa fa-edit" aria-hidden="true"></i> </button> <button type="button" aria-label="Delete item"> <i class="fa fa-trash" aria-hidden="true"></i> </button> <!-- Navigation --> <button onclick="history.back()">Back</button> <button>Next Step</button> <button>Skip to Results</button>

Dialog/Modal Buttons

<!-- Close buttons --> <button aria-label="Close dialog"> <span aria-hidden="true">&times;</span> </button> <!-- Confirmation --> <button>Confirm</button> <button>Cancel</button> <button>Save Changes</button>

Toggle Buttons

<!-- State toggles --> <button aria-pressed="false" aria-label="Mute audio"> <i class="fa fa-volume-up" aria-hidden="true"></i> </button> <!-- Visibility toggles --> <button aria-expanded="false" aria-controls="menu"> Show Menu </button>

Social Media Buttons

<!-- Share buttons --> <button aria-label="Share on Twitter"> <svg aria-hidden="true">...</svg> </button> <button aria-label="Share on Facebook"> <img src="fb-icon.png" alt="" aria-hidden="true"> </button> <!-- Like/favorite --> <button aria-label="Add to favorites" aria-pressed="false"> <i class="fa fa-heart-o" aria-hidden="true"></i> </button>

Media Control Buttons

<!-- Video controls --> <button aria-label="Play video"> <i class="fa fa-play" aria-hidden="true"></i> </button> <button aria-label="Pause video"> <i class="fa fa-pause" aria-hidden="true"></i> </button> <button aria-label="Mute"> <i class="fa fa-volume-up" aria-hidden="true"></i> </button> <button aria-label="Full screen"> <i class="fa fa-expand" aria-hidden="true"></i> </button>

Complex Examples

Button with Loading State

<button id="submit-btn"> <span class="btn-text">Save Changes</span> <span class="spinner" hidden aria-hidden="true"></span> </button> <script> function showLoading() { const btn = document.getElementById('submit-btn'); btn.querySelector('.btn-text').textContent = 'Saving...'; btn.querySelector('.spinner').hidden = false; btn.disabled = true; } </script>

Button Group

<div role="group" aria-label="Text formatting"> <button aria-label="Bold" aria-pressed="false"> <strong aria-hidden="true">B</strong> </button> <button aria-label="Italic" aria-pressed="false"> <em aria-hidden="true">I</em> </button> <button aria-label="Underline" aria-pressed="false"> <u aria-hidden="true">U</u> </button> </div>

Pagination Buttons

<nav aria-label="Pagination"> <button aria-label="Previous page"> <span aria-hidden="true">&laquo;</span> </button> <button aria-label="Page 1" aria-current="page">1</button> <button aria-label="Page 2">2</button> <button aria-label="Page 3">3</button> <button aria-label="Next page"> <span aria-hidden="true">&raquo;</span> </button> </nav>

Dropdown Button

<button aria-expanded="false" aria-haspopup="true" aria-controls="dropdown-menu"> Options <span aria-hidden="true">â–¼</span> </button>

Testing

Manual Testing

  1. Tab through all buttons on the page
  2. Verify each button has visible or accessible text
  3. Check that button purpose is clear
  4. Ensure icon buttons have appropriate labels

Screen Reader Testing

NVDA/JAWS: Tab to button
Expected: "Search, button" not just "button"

NVDA: Insert+F7, select Buttons
Expected: All buttons listed with descriptive names

Automated Testing

// Check all buttons have accessible names const buttons = document.querySelectorAll('button, [type="button"], [type="submit"], [role="button"]'); buttons.forEach(button => { const accessibleName = getAccessibleName(button); if (!accessibleName || accessibleName.trim() === '') { console.error('Button without accessible name:', button); } }); // Using axe-core const results = await axe.run(); const buttonViolations = results.violations.filter( v => v.id === 'button-name' );

Browser DevTools

// Find all buttons $$('button, [type="button"], [type="submit"], [role="button"]') // Check for accessible names Array.from(document.querySelectorAll('button')).map(btn => ({ element: btn, text: btn.textContent.trim(), ariaLabel: btn.getAttribute('aria-label'), ariaLabelledby: btn.getAttribute('aria-labelledby') }));

Automate Your Accessibility Testing

Our tool automatically checks for this rule and hundreds of other accessibility issues.

Start Your Free Trial