SVG Optimization and Accessibility Basics



This content originally appeared on dbushell.com (blog) and was authored by dbushell.com (blog)

SVG (Scalable Vector Graphics) are now commonplace on the web. When I wrote Resolution Independence With SVG for Smashing Magazine in 2012 many of the comments related to browser support and bugs. Back then we didn’t have Baseliness for peace of mind.

SVG adoption was a slow burn. We experimented with perverse icon fonts before it really caught on. SVG wasn’t well understood and neither were my “please export an SVG logo” requests.

Scooby-Doo mask reveal meme: in the first panel the villain labelled '.SVG' is masked. In the second panel the mask is removed to reveal SVG code with an embedded JPEG. The joke being it was a JPEG all along. Trust me it's funny...

A decade later and Smashing Mag is still publishing excellent new guides like Optimising SVGs by Andy Clarke. Sometimes you need to manually optimise SVG to preserve specific paths to be animated later. For basic optimisation the go to tool is SVGO.

SVGO

SVGO v4 was released this week with a migration guide.

SVGO and its various integrations will enable you to optimize SVGs and serve your web applications faster.

Behaviour from v3 to v4 has changed. The removeViewBox and removeTitle optimisations are no longer applied by default. This is a massive win! SVG elements need the viewBox attribute to scale responsively. Titles are good for accessibility. Both are a big deal for the web!

Previously with SVGO v3 I had to provide a config file to disable these options myself.

npx svgo --config ~/.svgo.js example.svg

Now with SVGO v4 I can finally delete that dotfile!

npx svgo example.svg

Jake Archibald made the SVGOMG web app if you prefer GUIs over CLIs. This still uses SVGO v3 but can be configured.

I’ve started to use Bun instead of Node to execute SVGO with a few extra options. Bun is a little faster to start up.

bunx svgo --pretty --indent=2 example.svg

These options add excess whitespace but a few extra bytes are negligible most of the time (more so after compression). I do this because I often copy and inline <svg> into HTML. I will add class attributes to specific elements and use my main stylesheet for styles and animations. Prettifying the code makes it easier to use.

The SVGO CLI isn’t documented very well but svgo --help provides more info.

Automation

Last week I found some old code whilst cleaning my Git repos.

Node packages.json with the dependency list meticulously organised using manual whitespace to line up version numbers.

I don’t want to speak about the formatting.

Grunt and Gulp were popular automated task runners. I published grunt-svg2png myself. I tried to build the project pictured above but some dependencies no longer exist. Typical. This type of task runner fell out of fashion as CI like Travis and GitHub Actions became popular as well as build/bundle tools like Vite.

In hindsight this work is slow and wasteful. It doesn’t make sense to optimise unchanged source files for every build and deployment. How often are SVGs edited?

Command line SVGO can batch process files using a wildcard:

npx svgo *.svg

Or the --folder and --recursive options:

npx svgo  --recursive --folder ./svg

I’ll throw away or simply overwrite the initial exported SVG. Of course, I keep the original Illustrator, Sketch, Figma, or Affinity Designer source. I’ve never deleted that and had to painstakingly recreate it to correct an error. Never.

My advice: optimise once and don’t get fancy with automated tooling.

Accessibile Inline SVG

When inlining SVG directly into an HTML document you can delete the XML prolog, the xmlns namespace, and most other attributes.

<svg width="50" height="50" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="50" />
</svg>

You need viewBox as discussed. The width and height attributes allow the browser to infer the correct aspect ratio and scale and avoid janky rendering. It’s not necessary to resize an SVG when exporting to the exact size it will be used. There are some nuances in floating point precision but generally it’s fine for the dimensions to differ from the viewbox.

SVG elements don’t have an alt attribute like images. The <title> element can be used to provide an accessible text description. Don’t use the title attribute by mistake (most browsers ignore it).

<svg width="50" height="50" viewBox="0 0 100 100">
  <title>An extremely abstract illustration of a black cat</title>
  <circle cx="50" cy="50" r="50" />
</svg>

Using the aria-label attribute is another option:

<svg aria-label="An extremely abstract illustration of a black cat" width="50" height="50" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="50" />
</svg>

I’ve seen it suggested to also add aria-labelledby:

<svg aria-labelledby="svg-title" width="50" height="50" viewBox="0 0 100 100">
  <title id="svg-title">An extremely abstract illustration of a black cat</title>
  <circle cx="50" cy="50" r="50" />
</svg>

Which still works but seems overkill. I’d avoid doing this.

⚠️ Excess ARIA is usually wrong! See the First Rule of ARIA Use.

Would aria-labelledby be useful if the SVG has visible text inside?

<svg aria-labelledby="svg-title" width="100" height="100" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="50" />
  <text x="20" y="50" id="svg-title" fill="white">Black cat?</text>
</svg>

This text will be read multiple times in succession by screen readers (at least from my testing with macOS VoiceOver in Chrome and Safari).

Adding role="img" improves that:

<svg role="img" aria-labelledby="svg-title" width="100" height="100" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="50" />
  <text x="20" y="50" id="svg-title" fill="white">Black cat?</text>
</svg>

VoiceOver will read the label but not descend into the <svg> unless specifically instructed to. Regardless, I would just avoid trying to be cute with aria-labelledby. Stick to aria-label or <title> for the SVG description.

Generally for inline SVG illustrations I opt for this format:

<svg role="img" aria-label="An extremely abstract illustration of a black cat">
  <circle cx="50" cy="50" r="50" />
</svg>

That feels very similar to using an <img> element. Using role="img" or not will depend on whether text inside the SVG needs to be accessible.

For purely decorative SVG like icons, using an aria-hidden attribute will completely remove it from the accessibility tree.

<svg aria-hidden="true" width="50" height="50" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="50" />
</svg>

When SVG is hidden there is no use for a label or title.

You may want some SVG icons to be accessible, but please spare screen readers from descriptions like “three lines that supposedly resemble a hamburger”. Hiding icons is fine if you have a text label like “Menu” alongside the icon.

Please have visible text labels with icons!

Accessibility is about comparable experiences.

The point is not to create a better experience, or even a good experience. It’s to ensure a comparable experience between different people.

Principles Of Web Accessibility

Accessibility works both ways. Providing a hidden label like “Menu” is great for assistive technology. Providing only an icon with abstract lines and no visible text sucks for everyone else.

Finally, using role="presentation" will only hide semantics from the <svg> parent. Focusable elements like <text> remain accessible.

<svg role="presentation" width="100" height="100" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="50" />
  <text x="20" y="50" id="svg-title" fill="white">Black cat?</text>
</svg>

This appears similar to <div>Black cat?</div> to assistive technology. Is that what you intended? Probably not.

As to which elements and attributes you should use for SVG accessibility depend on what experience you’re trying to give. What’s important is that you understand the implications and test thoroughly. Don’t take my word for it!

PS, I’m aware I’ve mixed both the correct and the American spellings of “optimise” 🙂


This content originally appeared on dbushell.com (blog) and was authored by dbushell.com (blog)