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.
<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>
.
<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 id
s 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:
<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 asHeart, 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>
.
<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 asHeart, 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!