Menu

Accessible SVGs

I know – you all love SVG. An SVG image can scale limitlessly, it's small in size, and it’s crisp (i.e. there are no artefacts). Most of the time you’ll use a SVG when it displaying an icon, be it a heart, a basket, a "thumbs up", an arrow or something else.

Most of you will implement the SVG image inline. It’s so simple. Just throw in the image and you're good to go. Wrong! Just because it’s a SVG and not an <img> doesn't mean you don't have to think about a few things when it comes to accessibility. Do you remember me praying to not forget the alt attribute within an <img> tag? You won’t be able to get away without something similar to the alt in a SVG.

Just use an image tag

That’s the simplest way to use a SVG. You can use an <img> tag and just refer to a SVG as source.

Hibiscus blossom
<img src="hibiscus.svg" alt="Hibiscus blossom" width="200">

That looks familiar and simple, and it’s certainly an option. But, alas, this solution will confuse your assistive device.

  • VoiceOver will recognise the SVG and announce it as Hibiscus Blossom, Group.
  • NVDA will read Graphic, Hibiscus Blossom.
  • Narrator reads Image, Hibiscus Blossom.
  • ChromeVox will announce Hibiscus Blossom. This could be copy or some kind of text. Who knows?

Image tag and ARIA role

To fix this, you’ll have to help the assistive technology by adding a role=img" to the <img>.

Hibiscus blossom
<img src="hibiscus.svg" alt="Hibiscus blossom" width="200" role="img">

That should do the trick. Now everyone will get the idea of an image.

  • VoiceOver will now recognise this image as Hibiscus Blossom, Image.
  • NVDA will read Graphic, Hibiscus Blossom.
  • Narrator reads Image, Hibiscus Blossom.
  • Chromevox will announce Hibiscus Blossom, Image.

The inline SVG

Okay, you could simply use the SVG code and put it on your page. (I've shortened the path so the code isn't too bloated.)

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd"
 clip-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5">
 <path d="M50 (...) 8.585z" fill="#dc0000" stroke="#dc0000"/>
</svg>

That'll do the trick. But not in a good way. Yes, in this case you can see the shape of a heart, but what about people who can't see? Don't forget about them.

  • VoiceOver will not recognise the SVG. For VoiceOver there is nothing.
  • NVDA will not recognise the SVG either. For NVDA there is nothing.
  • Narrator will read Image. What kind of image? Not very helpful.
  • Chromevox will not recognise the SVG. For Chromevox there is nothing.

Add aria-describedby for a better accessibility

You should extend your code and do something that will perform the same magic as an alt attribute within an <img>. First, you'll have to add a <title> to your code. The <title> should always come right after the opening <svg> and before the <path>. Now you’ll have to add aria-describedby to the <svg>. You can read about this aria-attribute on the page about aria-describedby.

Using an aria-describedby means you'll have to have one or more ids that actually describe something, in this case the SVG. Put an id on the <title> and maybe even add a description (<desc>) and give it an id as well. This will look something like this:

HeartThe shape of a red heart
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd"
 clip-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5"
 aria-describedby="my-title my-descrip">
 <title id="my-title">Heart</title>
 <desc id="my-descrip">The shape of a red heart</desc>
 <path d="M50 (...) 8.585z" fill="#dc0000" stroke="#dc0000"/>
</svg>
  • VoiceOver will read Heart, The shape of a red heart. VoiceOver identifies the SVG as Heart, Group.
  • NVDA will not recognise the SVG. For NVDA there is nothing.
  • Narrator reads Image, Heart.
  • Chromevox will read Heart, The shape of a red heart.

The final touch with ARIA role

To finish our accessible SVG, we'll add a role=img" like we did earlier with the <img>.

HeartThe shape of a red heart
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd"
 clip-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5"
 aria-describedby="my-title my-descrip" role="img">
 <title id="my-title">Heart</title>
 <desc id="my-descrip">The shape of a red heart</desc>
 <path d="M50 (...) 8.585z" fill="#dc0000" stroke="#dc0000"/>
</svg>
  • VoiceOver will read Heart, The shape of a red heart. VoiceOver will not recognise the SVG as an image. It identifies the SVG as Heart, Group.
  • NVDA will read Heart, Graphic, Heart. Don’t ask me why it announces the heart twice.
  • Narrator reads Heart, Image, The shape of a red heart.
  • Chromevox will read Image, Heart, The shape of a red heart.

Voilà! Your accessible SVG is ready. If you don't want the SVG to be part of the accessibility tree, I recommend a aria-hidden="true" on the SVG.

Did you know?

For performance reasons you (hopefully) always crush your JPGs and PNGs. The most popular tool is TinyPNG, but you can also use desktop apps like ImageOptim (for Mac). Many, many pages out there have big, bulgy SVGs. That's because a designer likely created the SVG in a vector-based app and simply saved the image as *.svg. Then it gets forwarded to the developer who uses the SVG "as delivered".

You can (and should) do better! Jake Archibald created the wonderful SVGOMG. Here you can throw in your fresh SVG straight out of the designer's office and "crush" it. Yes, with this nifty tool you'll get rid of all the unnecessary information within your SVG. Keep the internet fast, keep it small!