Skip to Content
Component ExamplesButtons

Overview

Buttons are one of the most common interactive elements on the web. When implemented correctly, screen readers announce them as “button” with their label, and users can activate them with Enter or Space. When implemented with a <div>, none of this works.

WCAG Criteria:

Key requirements:

  • Use the <button> element (not <div> or <span>)
  • Ensure keyboard focusability and activation (Enter/Space)
  • Provide a visible focus indicator
  • For toggle buttons, use aria-pressed to convey state
  • For icon-only buttons, use aria-label to provide an accessible name

Basic Button

Semantic Button vs. Div

Inaccessible
<div class="btn" onclick="handleClick()">Submit</div>
Live Preview
Submit
Accessible
<button type="button" class="btn">Submit</button>
Live Preview

What’s wrong with the div?

  • No button role announced by screen readers — it’s just text
  • Not focusable with Tab key
  • Cannot be activated with Enter or Space
  • No focus indicator visible
  • onclick only works with mouse

What the screen reader announces:

VersionAnnouncement
Inaccessible (<div>)“Submit” (just text, no role)
Accessible (<button>)“Submit, button”

Toggle Button

Toggle Button with State

Inaccessible
<!-- Label changes on toggle — confusing for SR users --> <button onclick="this.textContent = this.textContent === 'Mute' ? 'Unmute' : 'Mute'"> Mute </button>
Live Preview
Accessible
<!-- Stable label + aria-pressed conveys state --> <button aria-pressed="false" onclick="let p = this.getAttribute('aria-pressed') === 'true'; this.setAttribute('aria-pressed', String(!p))"> Mute </button>
Live Preview

What’s wrong with changing the label?

  • Screen reader users hear “Mute, button” then “Unmute, button” — the label change implies it’s a different button
  • With aria-pressed, the label stays “Mute” and the state changes: “Mute, toggle button, not pressed” → “Mute, toggle button, pressed”

Icon Button

Icon Button with Accessible Name

Inaccessible
<!-- No accessible name — SR says just "button" --> <button> <svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20"> <path d="M4 4L16 16M16 4L4 16" stroke="currentColor" stroke-width="2"/> </svg> </button>
Live Preview
Accessible
<!-- aria-label provides the accessible name --> <button aria-label="Close dialog"> <svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20"> <path d="M4 4L16 16M16 4L4 16" stroke="currentColor" stroke-width="2"/> </svg> </button>
Live Preview

What the screen reader announces:

VersionAnnouncement
Without aria-label”button” (no name — user has no idea what it does)
With aria-label”Close dialog, button”

Resources