Who it affects
A patient checking appointment status on a clinic booking page with a screen reader sees a green tick or red cross drawn in SVG. The tick is announced as image, so they do not know whether the booking went through.
Someone navigating by images with a screen reader rotor gets a list of entries all called image and cannot pick out the one they want.
Someone using a text-only or reader mode loses the SVG entirely and has no words to replace it.
Broken versus fixed
Both examples are live and rendered in a sandboxed frame. The broken one is deliberately broken.
Both rows are announced as: image, then the date. The listener cannot tell which appointment is confirmed and which is cancelled.
Announced: Confirmed, image, Tuesday 3 November. Then: Cancelled, image, Friday 6 November.
Show the markup behind both examples
<style>body{font-family:system-ui;padding:16px;margin:0}.status{display:flex;gap:10px;align-items:center;font-size:16px}svg{flex:none}</style>
<div class="status">
<svg role="img" width="28" height="28" viewBox="0 0 28 28"><circle cx="14" cy="14" r="13" fill="#2a8f4a"/><path d="M8 14l4 4 8-8" stroke="#fff" stroke-width="3" fill="none"/></svg>
<span>Tuesday 3 November, 10:40, Dr Okafor</span>
</div>
<div class="status">
<svg role="img" width="28" height="28" viewBox="0 0 28 28"><circle cx="14" cy="14" r="13" fill="#c0392b"/><path d="M9 9l10 10M19 9L9 19" stroke="#fff" stroke-width="3"/></svg>
<span>Friday 6 November, 14:10, Nurse Patel</span>
</div>
<style>body{font-family:system-ui;padding:16px;margin:0}.status{display:flex;gap:10px;align-items:center;font-size:16px}svg{flex:none}</style>
<div class="status">
<svg role="img" aria-labelledby="ok1" width="28" height="28" viewBox="0 0 28 28"><title id="ok1">Confirmed</title><circle cx="14" cy="14" r="13" fill="#2a8f4a"/><path d="M8 14l4 4 8-8" stroke="#fff" stroke-width="3" fill="none"/></svg>
<span>Tuesday 3 November, 10:40, Dr Okafor</span>
</div>
<div class="status">
<svg role="img" aria-labelledby="no1" width="28" height="28" viewBox="0 0 28 28"><title id="no1">Cancelled</title><circle cx="14" cy="14" r="13" fill="#c0392b"/><path d="M9 9l10 10M19 9L9 19" stroke="#fff" stroke-width="3"/></svg>
<span>Friday 6 November, 14:10, Nurse Patel</span>
</div>
Test it by hand in two minutes
- In DevTools, search the Elements panel for <svg. For each one that carries meaning, look for a title element as its first child, or an aria-label or aria-labelledby on the svg itself.
- Check the svg has role="img". Without it, some screen readers read every path and text inside the drawing separately, or skip it.
- Turn on VoiceOver (Cmd+F5) or NVDA and arrow through. A meaningful SVG should be announced as a short name followed by image; a decorative one should not be announced at all.
- Hover the SVG in Chrome. If you used a title element you will see it as a tooltip, which is a quick way to confirm it is wired up.
The fix
<svg role="img" viewBox="0 0 28 28">
<circle cx="14" cy="14" r="13" fill="#2a8f4a"/>
</svg>
<svg role="img" aria-labelledby="t1" viewBox="0 0 28 28">
<title id="t1">Confirmed</title>
<circle cx="14" cy="14" r="13" fill="#2a8f4a"/>
</svg>
The title element must be the first child of the svg, and pointing aria-labelledby at it makes every browser and screen reader combination pick it up. A plain aria-label on the svg also works.
Logo in a link
<a href="/"><svg role="img" viewBox="0 0 120 40">...</svg></a>
<a href="/"><svg role="img" aria-label="Hollowbrook Clinic home" viewBox="0 0 120 40">...</svg></a>
When the SVG is the only thing in the link, its name becomes the link text, so say where the link goes.
Decorative icon next to text
<svg role="img" viewBox="0 0 20 20">...</svg> Download the form
<svg aria-hidden="true" focusable="false" viewBox="0 0 20 20">...</svg> Download the form
The words already say it. Hide the icon and drop the role. focusable="false" stops older browsers tabbing to it.
Common mistakes
- Adding a title element but putting it after the shapes, where some tools ignore it.
- Using a desc element on its own; desc is a longer description and does not provide the name.
- Writing alt="..." on the svg element, which does nothing.
- Naming a decorative icon (aria-label="icon") instead of hiding it with aria-hidden="true".
- Leaving role="img" off and expecting the title to be read reliably.
WCAG criteria this rule maps to
axe-core tags this rule as WCAG 2.2 level A. It is one of the checks for:
- 1.1.1 Non-text Content (level A). Official W3C text.
The scanner can see an svg with role img, graphics-document or graphics-symbol that has no title child, aria-label or aria-labelledby. It cannot tell whether an SVG without a role should have had one, or whether the name says what the drawing means. Automated tools find only part of the accessibility problems on a page; a pass on this rule is not a pass on the criterion.
Related rules
image-alt
Every img element needs an alt attribute.
A
Elements with role="img" must have alternative text
role-img-alt
Adding role="img" to a div, span or any other element tells assistive technology to treat it as a single picture.
A
Embedded objects must have alternative text
object-alt
The object element embeds another file in the page: a PDF timetable, an SVG, an audio file, sometimes a whole page.
A
ARIA roles must be valid
aria-roles
The role attribute only accepts names from the ARIA list: dialog, button, tab, listbox and so on.