Icon-only buttons without a visible label are an easy win — or an easy fail. This guide covers aria-label, visible tooltip pattern, toggle (aria-pressed), and toolbar groups with roving tabindex, in Vanilla and React.
WCAG: 1.1.1 · 2.5.5 · 4.1.2 · 1.4.11
Level: Beginner → Intermediate
Icons: lucide-react
1
What we are building
Icon-only buttons that keep their meaning for keyboard, screen reader, voice and touch users.
What we are building
An icon-only button is a <button> whose visible content is an SVG. Because there is no visible text, the button needs its accessible name supplied some other way — typically aria-label. We also cover the three follow-on patterns you will meet in production: tooltips, toggles and toolbars.
2
The 4 real-world variants
Switch between the inaccessible version and the accessible fix.
The 4 real-world variants
Variant A
Standard icon button
Preferred
Live example
Deleted 0 times
HTML · Accessible
<!-- aria-label on the button; svg hidden from AT -->
<button type="button" aria-label="Delete item">
<svg aria-hidden="true" focusable="false">
<!-- trash icon -->
</svg>
</button>
aria-label gives the button its accessible name. The SVG becomes decorative with aria-hidden='true' and focusable='false' (for old IE).
Variant B
With visible tooltip
Common UI
Live example
Hover or Tab to focus — tooltip appears for both; Escape dismisses.
HTML · Accessible
<!-- Visible tooltip + aria-label fallback.
Tooltip appears on hover AND focus (WCAG 1.4.13), dismissable with Escape. -->
<div class="tt-wrap">
<button
type="button"
aria-label="Share this page"
aria-describedby="tip-share"
>
<svg aria-hidden="true" focusable="false"><!-- share icon --></svg>
</button>
<div role="tooltip" id="tip-share" class="tooltip">
Share this page
</div>
</div>
title='...' is NOT a real tooltip (fails on touch, random delays). Build a visible one on hover/focus and keep aria-label as the SR fallback.
Variant C
Toggle (aria-pressed)
Preferred
Live example
Bookmark: not saved · Audio: on
HTML · Accessible
<!-- aria-pressed makes it a toggle; SR says "pressed" / "not pressed" -->
<button
type="button"
aria-label="Bookmark this article"
aria-pressed="false"
onclick="toggleBookmark()"
>
<svg aria-hidden="true" focusable="false"><!-- bookmark --></svg>
</button>
<!-- Alternative: change the label itself with state -->
<button type="button" aria-label="Mute audio" onclick="toggleMute()">
<svg aria-hidden="true" focusable="false"><!-- speaker icon --></svg>
</button>
aria-pressed turns a button into a two-state toggle (pressed / not pressed). Alternatively, change the aria-label itself with state.
Variant D
Toolbar group
Advanced
Live example
Tab into the toolbar, then use ←→ (and Home / End) to move between buttons.