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>
Button vs Link
- 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;
}