Accessible SVG Icons in React: A Practical Guide
How to make SVG icons accessible in React apps — hiding decorative icons from screen readers, labelling interactive icons, and testing with NVDA, VoiceOver, and axe.
Icons seem simple, but they are a surprisingly common source of accessibility issues. An icon inside a button with no visible label, an SVG that reads out its path data, a tooltip that never reaches keyboard users — any of these can break an otherwise solid experience.
This guide covers every pattern you will encounter when using SVG icons in a React app.
Rule 1: Decorative icons must be hidden
An icon that sits next to visible text carries no additional information. A screen reader that reads both the icon's internal content and the label text creates noise.
// ✅ Hidden from screen readers — icon is decorative
<button>
<Home size={16} aria-hidden="true" />
Dashboard
</button>
krz-icons sets aria-hidden="true" on every icon by default, so this works out of the box. You do not need to add it yourself unless you override the prop.
Rule 2: Icon-only interactive elements need a label
A button that contains only an icon has no accessible name unless you provide one. Use aria-label on the button — not on the icon.
// ✅ Button has an accessible name
<button aria-label="Close dialog">
<X size={18} />
</button>
// ❌ The icon's aria-hidden removes it from the tree,
// leaving the button with no accessible name
<button>
<X size={18} />
</button>
Rule 3: Standalone informational icons need a title
Occasionally an icon carries meaning without adjacent text — a status dot, a severity badge, a checkmark in a table cell. Use the title prop (krz-icons), which adds a <title> element inside the SVG and sets role="img".
// ✅ Reads "Verified" to screen readers
<CheckCircle size={20} title="Verified" />
This generates:
<svg role="img" aria-hidden={undefined} ...>
<title>Verified</title>
<!-- paths -->
</svg>
Rule 4: Tooltips must be keyboard-accessible
CSS-only :hover tooltips are invisible to keyboard and touch users. If you add a tooltip to an icon button, make sure it also appears on :focus-visible.
// Tailwind example — shows on hover AND focus
<div className="group relative">
<button aria-label="Settings" className="focus-visible:ring-2 ...">
<Settings size={18} />
</button>
<div
role="tooltip"
className="invisible group-hover:visible group-focus-within:visible ..."
>
Settings
</div>
</div>
Rule 5: Icon color contrast
If an icon communicates meaning (not just decoration), its stroke color must have at least a 3:1 contrast ratio against the background (WCAG 1.4.11 — Non-text contrast). Run icons through a contrast checker if you use subtle grey tones.
Rule 6: Do not rely on color alone
Status icons (error, warning, success) must not use color as the only differentiator. Pair the color with a shape — a different icon per status — so colorblind users can tell them apart.
// ✅ Shape AND color differ
{status === "error" && <AlertCircle color="red" title="Error" />}
{status === "warning" && <AlertTriangle color="amber" title="Warning" />}
{status === "success" && <CheckCircle color="green" title="Success" />}
// ❌ Only the color changes — shape is the same
{status === "error" && <Circle color="red" />}
{status === "warning" && <Circle color="amber" />}
{status === "success" && <Circle color="green" />}
Testing icons for accessibility
- axe DevTools — browser extension that catches missing labels on icon buttons automatically.
- VoiceOver (macOS/iOS) — press Cmd + F5, then Tab through the page. Icon buttons without a label will read the button's element type with no name.
- NVDA (Windows) — free screen reader, Tab through the page to hear how buttons are announced.
- Keyboard navigation test — unplug your mouse and Tab through the page. Every interactive icon must be reachable and usable.
Summary checklist
- Decorative icons:
aria-hidden="true"(krz-icons default). - Icon-only buttons:
aria-labelon the<button>. - Standalone informational icons:
titleprop. - Tooltips: visible on focus as well as hover.
- Status icons: different shapes, not just different colors.