Accessible SVG Icons with Inline Sprites

If we were searching for reasons to use icons on a project, we don’t need to go far in order to find them. As Oliver Reichenstein wonderfully put it in his talk “On Icons” at Smashing Conference Freiburg 2016: “At a certain stage in a project someone always comes in and says: ‘We need icons!'” And why do people need icons?

  • Icons help dealing with narrow spaces, where text doesn’t fit,
  • They can have a common meaning independent of language,
  • They brand,
  • They can help draw attention to important elements in a user interface.

Whatever the reasoning may be, we need to make sure that people who can’t see or recognize these icons can understand their purpose.

Sign for a hand dryer misinterpreted as: Push Button, Receive Bacon
Push Button, Receive Bacon – it’s so obvious.

Standing on their own, icons can be misinterpreted as Mallory van Achterberg shows in her talk at the Fronteers Conference in 2017. The most important issue though: they lack text. Text is the most accessible format for information on the web. Screen readers understand text best and the same applies to most assistive technology, such as translation apps and Braille displays. So, if we have anything on our web page that’s not text — like icons — we must add text that gives our users the same information. Otherwise we could exclude people from understanding our interfaces.

A sign at a cliff stating: if you see someone drowning, call 911
First thing you do when you see someone drowning?

How to use an SVG graphic as an icon

A Scalable Vector Graphic (SVG) is an Extensible Markup Language (XML) specification, which can be embedded into an HTML document using the <svg> element like the following code example (the SVG in the example would show an arrow pointing downwards in a browser):

<body>
  <svg viewBox="0 0 10 10">
     <path d="m5 9-3-4h2v-4h2v4h2z"/>
  </svg>
</body>

This basic example shows all we need to display an SVG in supporting browsers. However, without adding a set height or width, an SVG will render as large as the viewport will allow. This can be remedied as demonstrated in the following example:

<body>
    <svg width="10" height="10" viewBox="0 0 10 10">
        <path d="m5 9-3-4h2v-4h2v4h2z"/>
    </svg>
</body>

A single CSS helper class addressing our SVG icons to make them behave, will then override those values and scale our icons perfectly in place with the text:

.svg-icon {
    /* Place the icon on the text baseline. */
    position: relative;
    top: .125em;

    /* Prevent the icon from shrinking inside a flex container. */
    flex-shrink: 0;

    /* Scale the icon to match the font-size of the parent element. */
    height: 1em;
    width: 1em;

    /* Let the icon take whatever color the parent has. */
    fill: currentColor;

    /*
     * If the icon is used on a link, which has a color transition,
     * we can also use a transition on the fill value.
    */
   transition: fill .3s;
}

with the following HTML:

<body>
    <svg class="svg-icon" width="10" height="10" viewBox="0 0 10 10">
        <path d="m5 9-3-4h2v-4h2v4h2z"/>
    </svg>
</body>

I created a Codepen to demonstrate what this single CSS selector can do. We can raise or lower the font-size value of the paragraph in the CSS panel and see the icon scale with the text. We can also change the color value and see how the adjustment affects the icon. Hovering over or setting focus to the link shows the transition on both the link color and the fill color of the icon.

This issue being solved we are faced with another: we lack a textual equivalent to the graphic we added to our webpage.

Making an SVG icon accessible

Let’s assume we need to create a button, which toggles the visibility of a site-wide navigation. On activation the navigation will be revealed and on repeated activation it will be hidden again. As we all agree, hopefully, a button is a button and should therefore be a <button>. In our example case, screen estate is scarce and all we are given by our designer is a hamburger icon; no text. Switching to our editor, we add the SVG code to the button:

<button type="button">
    <svg viewBox="0 0 10 10"
         height="10"
         width="10"
         role="img"
         class="svg-icon">
        <path d="m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z"/>
    </svg>
</button>

Now, we need to add some text, which can be interpreted by assistive technology. Using this inline SVG, we are able add a <title> element as the first child of the <svg> and give it an ID. Then we want to reference the ID value with aria-labelledby on the opening <svg> tag itself:

<button type="button">
    <svg viewBox="0 0 10 10"
         role="img"
         class="svg-icon"
         aria-labelledby="menu-icon-title"
         focusable="false">
        <title id="menu-icon-title">Menu</title>
        <path d="m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z"/>
    </svg>
</button>

Now, when someone sets focus on the button, the text in the title element will be announced (some browsers even show the text inside the <title> element in a tooltip on hover). Notice the focusable="false" attribute-value combination on the SVG! It will prevent Internet Explorer and Microsoft Edge from focusing the SVG. This is a bug and won’t be fixed for Internet Explorer 10 and 11.

We got Options

There is another way to add an invisible textual equivalent next to an icon, which needs slightly more HTML and a CSS helper class to work just like the example above.

You may use a <span> element to wrap the text and then visually hide it from the viewer. That way we provide assistive technology with a textual explanation without changing the design of the button.

<button type="button">
    <svg class="svg-icon"
         height="10"
         width="10"
         viewBox="0 0 10 10"
         aria-hidden="true"
         focusable="false">
        <path d="m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z"/>
    </svg>
    <span class="visually-hidden">
        Menu
    </span>
</button>

The CSS for the helper looks like this:

.visually-hidden {
    // Move the text out of the flow of the container.
    position: absolute;

    // Reduce its height and width to just one pixel.
    height: 1px;
    width: 1px;

    // Hide any overflowing elements or text.
    overflow: hidden;

    // Clip the box to zero pixels.
    clip: rect(0, 0, 0, 0);

    // Text won't wrap to a second line.
    white-space: nowrap;
}

A not too different example

Now, our designer decided to add another button with text and the icon next to it. If you have text next to the icon, don’t add a title and description but replace the role="img" on the SVG with aria-hidden="true". Setting aria-hidden="true" on an element removes it and its children from the accessibility tree altogether. It will not be exposed to the accessibility API:

<button type="button">
    Menu
    <svg viewBox="0 0 10 10"
         class="svg-icon"
         aria-hidden="true"
         focusable="false">
        <path d="m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z"/>
    </svg>
</button>

Because we have text explaining the purpose of the button, now, the icon became mere decoration. When dealing with a decorative image or graphic, we want to hide it from the accessibility tree.

Here is a Codepen showing the four different button examples: Making a button with an SVG icon accessible.

Focusing the button, screen readers will announce: “Menu, Button”. Perfectly fine. Yet now, visually, we got the same SVG but with different code. Imagine you’d have to add it in another context, where the textual labels of the first version do not apply. And then another one. And another one. That’s not ideal.

We need to be careful not to mistake aria-hidden="true" for role="none"! They have different use-cases and different outcomes. Scott O’Hara wrote a wonderful article on when to use which attribute.

Write less code – use a sprite

Now, to quote Heydon Pickering from his talk “Writing Less Damn Code” from Beyond Tellerrand Berlin 2016: “Less code is better code! It’s less code to think about. Less code to maintain. Less data to transfer over the network.”

Imagine the following situation: a few weeks into the project our designer is all over the place with their icons. We got a lot of different ones to deal with and different use-cases (some need a title and description while others need to be hidden). It would take a long time to create the icons with all the different attributes and text variations. Hence, along came the SVG sprite and saved us hours of time!

By placing an empty <svg></svg> as the first child of the opening <body> tag and hiding it from everyone (sighted or not) with an inline-style of display: none, we created a space to put our icons. It’s important to place the display: none setting inline, as we do not want the sprite to be visible while the browser is loading our stylesheet! If you had styles in the critical path of your website though, you may add it there and leave the sprite alone.

<body>
    <svg style="display:none;">
    </svg>
</body>

Note: This is only one way to create a sprite with SVG! There are several ways to do this but it is completely out scope for this article to explain them all. Sara Soueidan shows in great detail the different ways to create an SVG sprite with all advantages and issues that come with each approach.

When using an SVG sprite for an icon system you will want to leave the SVG itself alone. As you are going to reference the single icon in an <svg> element, you will want to label it there or hide it where it is only a decorative graphic.

Generally speaking, one should always optimize their SVG code no matter the use-case. Luckily for us, as this is the web and there’s a million tools by a million people, we don’t need to do the legwork ourselves.

SVGO(MG)

There are different ways to optimize and simplify our SVG code before adding it to our HTML document. The first and most rewarding is to open the SVG in an editor and remove all unneeded clutter by hand. It is also the most time-consuming option and the chance to remove something we might have needed is pretty high. While I can recommend to do this once or twice in order to get a better understanding of the structure of an SVG file, I do not advise to go this route forever.

For those preferring the command line over a graphical interface there is svgo. We can pass it a good bunch of arguments on how to optimize the SVG, what to remove, what to leave in place and even what to enforce to stay. For people preferring task runners there is ‘grunt-svgmin‘ built on top of SVGO and ‘gulp-svgmin‘ just the same. Sara Soueidan wrote a wonderful overview of existing tools with advantages and disadvantages for each.

If we were more into graphic user interface tools, there is SVGOMG by Jake Archibald. It’s as powerful as the command line variant, while also showing the output (code and visual). Instead of writing flags on our terminal, we may tick or untick options from the right side panel. One caveat is that it can become quite tedious to upload and optimize each icon separately.

Building the sprite

Before running our icons through SVGOMG, they may look like the following, depending on the software you use to create the graphic (the example shows SVG code output through the “Save as” option of Adobe Illustrator):

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In. 
SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="24px" height="24px" viewBox="0 0 24 24" 
enable-background="new 0 0 24 24" xml:space="preserve">
    <path d="M7.41,8.59L12,13.17l4.59-4.58L18,10l-6,6l-6-6L7.41,8.59z"/>
    <path fill="none" d="M0,0h24v24H0V0z"/>
</svg>

Note: using the “Export” option of Adobe Illustrator generates much cleaner SVG code!

Opening the SVG in the browser, we see a 90 degree angle pointing downwards. Running this code through SVGOMG with all checkboxes ticked, we get a nice, minified SVG file. Note: the only checkbox you need to leave unchecked is the “Remove ViewBox”! We always need to keep the viewBox attribute for the icon to work properly!

See what is left after the optimization:

<svg viewBox="0 0 24 24">
   <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
   <path fill="none" d="M0 0h24v24H0V0z"/>
</svg>

Placing the SVG code in the sprite, we need to adjust the code only slightly (I put the line-breaks back in for better readability):

<svg style="display: none">
    <symbol id="icon-angle-down"
            viewBox="0 0 24 24">
        <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
        <path fill="none" d="M0 0h24v24H0V0z"/>
    </symbol>
</svg>

We replace svg with symbol and give the icon a unique ID, so we can reference it in other parts of our document. The biggest advantage of this approach is the resulting ability to re-use an icon ad infinitum and label it per use-case. No need to create one icon more than once:

<button type="button">
    <svg role="img"
         class="svg-icon"
         aria-labelledby="toggle-nav-icon-title"
         focusable="false">
        <title id="toggle-nav-icon-title">Menu</title>
        <use xlink:href="#icon-angle-down" />
    </svg>
</button>

<a href="#next-section">
    <svg role="img"
         class="svg-icon"
         aria-labelledby="next-section-icon"
         focusable="false">
        <title id="next-section-icon">Jump to the next section</title>
        <use xlink:href="#icon-angle-down" />
    </svg>
</a>

<button type="button">
    <svg class="svg-icon"
         aria-hidden="true"
         focusable="false">
        <use xlink:href="#icon-angle-down" />
    </svg>
    Push The Button Down!
</button>

<p>
    Scroll further down for more information!
    <svg class="svg-icon"
         aria-hidden="true"
         focusable="false">
        <use xlink:href="#icon-angle-down" />
    </svg>
</p>

With the self-closing <use> element, we can reference the ID of the SVG symbol in the sprite. In the example code above we have four different use-cases, all with the same icon, now. We may hide it, when it’s used as decoration or we can label it where needed. Cool!

Look at the Codepen I created! There is no visual difference between the icons but the difference of the amount of code used to display them is tremendous.

Wrapping up

I hope you found this tutorial useful and are now able to use SVG icons in your projects. It is impossible to explain all the underlying concepts in this one little article though. In order to help you starting a deep-dive into the SVG ocean, I will provide you with some links to other articles and books on the subject.

Further Resources

Acknowledgements

What I’ve gathered in knowledge over the years when we talk about SVG and also accessibility on the web, comes from reading books, blog posts and tutorials. Also following those people, who are working in this field of our industry and have written those books, blog posts and tutorials. Of course endless tinkering, making and breaking things during late hours with a computer in front of my nose helped as well.

So, without any further ado: thanks to Léonie Watson, Laura Kalbag, Lea Verou, Estelle Weyl, Sara Soueidan, Sarah Drasner, Val Head, Seren Davies, Marcy Sutton, Carie Fisher, Zoe Gillenwater, Heather Migliorisi, Mallory van Achterberg, Steve Falkner, Patrick Lauke, Heydon Pickering, Neil Milliken, Nicolas Steenhout, Marco Zehe, Hugo Giraudel, Gunnar Bittersmann, Eric Bailey, Alan Dalton, Chris Coyier, Bruce Lawson, Aaron Gustafson, Scott O’Hara, the whole Paciello Group, AListApart and ABookApart, Kahn Academy, the team behind Pa11y, the team behind tenon.io and many whom I’ve forgot to mention here.

Thanks to all those people, who (hopefully) will never get tired of showing others (and me) why accessibility is important and how to make the web a more inclusive place.

Marco Henstenberg

About Marco Hengstenberg

Marco Hengstenberg is passionate about accessibility, performance and embraces progressive enhancement when building responsive websites. Bringing inclusive solutions and an eye for user experience to the table, he is a Front End Developer at land in sicht (site's currently in German language only).

When not writing Front End code, he loves to casually play Basketball outside and Starcraft 2 inside. He is a father of two daughters and happily married. Marco tweets and toots. He's in love with music and dancing and always carries a book with him.

12 comments on “Accessible SVG Icons with Inline Sprites”

  • Most SVGs have a title attribute and it can make sense to reference it from the svg element itself if it’s already there to avoid extra transforms (although you still have to give the title an ID, but when using a sprite, it seems counterproductive as you’ve have to re-add a title element on top of the use one. Imho, in that case, and since the title would be a single string anyway, it would make more sense to use aria-label="Your title" instead of aria-labelledby and ditch the title altogether. Your markup will be a bit lighter and easier to maintain. What are your thoughts on this?

    • Marco Hengstenberg says:

      Wonderful thought Thibaut! This is indeed another viable solution to add text to an icon, where the text does not need to be visible but accessible to assistive tech.

      Depending on the implementation/CMS/requirements on a project it might be harder to translate the value in the aria-label attribute though. Text inside the title tag can be accessed a tad easier by translation tools from my experience. Yet, I have no statistics or anything to back that up at the moment.

    • Marco Hengstenberg says:

      Using Pseudo-Elements is … complicated in this context. While you can add an icon (as a background-image for example) and give users text inside the content attribute in CSS there are some browser-screen reader combinations which will ignore the content of your pseudo-element.

      So, while it is possible to use SVG (base64 encoded for example) in your CSS and then in a Pseudo-Element, it might prove to be less accessible than using it directly in the HTML. You might want to add a textual equivalent anywhere close to the icon or, heavily depending on your markup structure around the pseudo-element, add some accessible text elsewhere with aria-label or something equivalent.

      I once went and tried several solutions to the problem of having only an icon (in whatever form) and the need to add text to it: Example Codepen hope that helps.

  • I think the white-space: nowrap; deserves a bit more explanation for its presence. It might seem weird to worry about text wrap when it is visually hidden, but it has an effect on screen readers. The narrow box can cause a screen reader to read all the the text letter by letter as the browser wraps it at the smallest possible unit. This style declaration prevents that from happening.

  • I investigated this option when implementing SVG icons for our legacy codebase, but for our particular use case it didn’t turn out to be the best approach. Instead we decided to inline the icons as backgrounds in a CSS file. I wrote a small nodejs tool to convert a directory of icons into a CSS file: https://www.npmjs.com/package/ikode

    I fully agree that the method you describe above is nicer and offers easier possibilities to change the icons dynamically or use some simple CSS rules to change colors, but if there are others that cannot use this approach for some reason then perhaps they may my library useful.

    • Marco Hengstenberg says:

      Heya rotous, I believe there is rarely (if not never) a single solution to a problem in life or in web development. I’m glad you found a way to work around the constraints of your legacy codebase!

  • Lucas Padovan says:

    I may have missed something but as you are still using ID for the title, this creates an issue when trying to use the same svg in several places in the same page if you create a reusable file/component only for that svg. As stated before, the aria-label approach solves this issue but we have found that there are `masks` use id to refer a previous element. Do you know if there is a way to create those masks without inquiring in an element with ID that will generate duplicity issues?

  • Great write-up. What would you recommend if I am wrapping my SVG in a container element. For example, if I’m creating a custom element in a framework like Angular, where my SVG is not exposed directly, but is, instead, inside an “app-icon” component. So, the DOM hierarchy might look like:

    Button –> App-Icon –> SVG

    In such a case, does the App-Icon need anything special for accessibility? Or, will it have “none” by default, be treated like a simple container, and then the SVG is exposed inherently? In other words, does the App-Icon container need any role / aria attributes (assuming I don’t apply aria-hidden).

Comments are closed.