I ran an accessibility audit on a client's marketing site last year. The Lighthouse score was 98. But when I turned on VoiceOver and tabbed through the navigation, every single icon was invisible. Seven SVG icons — home, search, cart, account, menu, close, arrow — all announced as nothing. Just silence between the text links. That's when I realized: almost nobody teaches designers how to make SVGs accessible. Here's what I've learned since.
By default, an SVG element with no accessibility attributes is treated inconsistently by screen readers. Some ignore it entirely. Some announce "image" with no context. Some read the file name if it was loaded via <img>. None of these behaviors are useful. A search icon that announces as "search-icon.svg" is arguably worse than one that says nothing — at least silence doesn't interrupt the flow.
The W3C's SVG Accessibility API Mappings spec defines how SVG should map to platform accessibility APIs, but browser implementation is inconsistent. Relying on defaults means you're probably shipping broken accessibility.
After testing across VoiceOver (macOS), NVDA (Windows), and JAWS (Windows), I've settled on three patterns that work reliably. Which one to use depends on what the SVG is doing.
If the SVG is purely decorative — a background pattern, a divider, an illustration that adds no information — hide it from screen readers entirely:
<svg aria-hidden="true" focusable="false"> <!-- decorative shapes --> </svg>
When I use this: Section dividers, background blobs, decorative illustrations, repeated icons that already have a text label right next to them.
If the SVG conveys meaning — a chart, a diagram, an illustration with instructional content — add a <title> element and reference it with aria-labelledby:
<svg aria-labelledby="chart-title" role="img"> <title id="chart-title">Revenue grew 34% from Q1 to Q4 2025</title> <!-- chart paths --> </svg>
This ensures the screen reader announces "Revenue grew 34% from Q1 to Q4 2025 — image" instead of reading out path coordinates or remaining silent.
If the SVG is clickable — an icon button, a link — use aria-label on the interactive element, not on the SVG itself:
<button aria-label="Open shopping cart (3 items)">
<svg aria-hidden="true" focusable="false">
<!-- cart icon paths -->
</svg>
<span class="sr-only">3 items in cart</span>
</button>
I use aria-hidden="true" on the SVG inside a button because the button's aria-label already describes the action. Announcing both would be redundant. The .sr-only text provides a visible-on-focus fallback for users who navigate by keyboard but don't use a screen reader.
I test every icon system I build with three screen readers, in this order:
If an icon passes VoiceOver and NVDA, it's probably fine. I don't own JAWS (it costs $95/year), but a client once tested my work with it and reported no issues with these patterns.
aria-hidden="true"<title> + aria-labelledbyaria-label on the button/link, hide the SVG<img>? → Always add alt text<use> instance needs its own aria-labelNeed to export accessible SVGs? Use SVG2PNG to convert SVGs to PNG for email clients and legacy apps that don't support inline SVG — and always provide alt text on the resulting <img> tag.