Skip to main content

Accessible Buttons

How to create accessible button components.

Semantic HTML

Always prefer native <button> elements over divs or spans with click handlers.

// Good: Native button
<button onClick={handleClick}>Submit</button>

// Bad: Div pretending to be a button
<div onClick={handleClick}>Submit</div>
  • Use <button> for actions (submit, toggle, open modal)
  • Use <a> for navigation to a new page or resource
// Action: Use button
<button onClick={() => setOpen(true)}>Open Settings</button>

// Navigation: Use link
<a href="/settings">Go to Settings</a>

Accessible Names

Every button needs an accessible name.

// Text content provides the name
<button>Save Changes</button>

// Icon buttons need aria-label
<button aria-label="Close dialog">
<CloseIcon aria-hidden="true" />
</button>

// Or use visually hidden text
<button>
<CloseIcon aria-hidden="true" />
<span className="visually-hidden">Close dialog</span>
</button>

States

Disabled State

// Use the disabled attribute, not aria-disabled for most cases
<button disabled>Cannot Submit</button>

Loading State

<button aria-busy={isLoading} disabled={isLoading}>
{isLoading ? 'Saving...' : 'Save'}
</button>

Toggle Buttons

<button
aria-pressed={isActive}
onClick={() => setIsActive(!isActive)}
>
Bold
</button>

Focus Styles

Never remove focus outlines without providing an alternative.

/* Bad: Removes focus indicator */
button:focus {
outline: none;
}

/* Good: Custom focus style */
button:focus-visible {
outline: 2px solid var(--focus-color);
outline-offset: 2px;
}