Images Rule

SVG graphics with an image role must have alternative text svg-img-alt

Inline SVG is drawn with code rather than loaded as a file, so there is no alt attribute to fill in. When the svg element has role="img" (or a graphics role), screen readers treat it as a picture and need a name from a title element, aria-label or aria-labelledby. Without one, a logo, status icon or chart is read as image or ignored.

Websight reports this as <svg> elements with an img or image role must have alternative text

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.

Broken

Both rows are announced as: image, then the date. The listener cannot tell which appointment is confirmed and which is cancelled.

Fixed

Announced: Confirmed, image, Tuesday 3 November. Then: Cancelled, image, Friday 6 November.

Show the markup behind both examples
Broken
<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>
Fixed
<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

  1. 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.
  2. Check the svg has role="img". Without it, some screen readers read every path and text inside the drawing separately, or skip it.
  3. 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.
  4. 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

Before
<svg role="img" viewBox="0 0 28 28">
  <circle cx="14" cy="14" r="13" fill="#2a8f4a"/>
</svg>
After
<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

Before
<a href="/"><svg role="img" viewBox="0 0 120 40">...</svg></a>
After
<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

Before
<svg role="img" viewBox="0 0 20 20">...</svg> Download the form
After
<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:

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.

Scan your own page for this and 63 other checks

Open any page in Chrome, click Websight, and get a score out of 100 with every issue explained and a draft fix snippet per element.

Add Websight to Chrome

Free scans, no account, nothing leaves your computer. Automated tools find only part of the problems; the extension says so on every report.