A Quick Update on Our SVG Icon Process
At 4/19/2024
In February of 2016, I shared what was then our SVG icon process. But as our techniques evolve with each new project, those steps feel less and less representative of what we usually do. So, here’s an update!
What We Did Then
After preparing, cleaning and exporting our icons to a folder (which we still do today), we would squash those individual files into a single SVG sprite via a step of our build process. Then, we’d reference that file via a <use>
element, with a hash representing the specific icon to display:
<svg class="Icon">
<use xlink:href="icons.svg#back"/>
</svg>
Code language: HTML, XML (xml)
This seemed to work well, but gradually over the course of several projects we encountered some challenges:
- The SVG sprite sometimes wouldn’t load unless it was hosted in a certain place or with certain security settings.
- It required a polyfill to work in older browsers.
- Individual instances of icons on a page could not have their child elements styled independently for animations, toggles, etc.
- The accessibility features we introduced were challenging to maintain in lockstep, and were too far removed from the context of how and where an icon was displayed.
- As a project grew, team members wouldn’t always know when to start a new sprite or build on a previous one. A collection of country flags or payment types might dramatically increase the size of the core icon sprite, even on pages where they were never displayed.
What We Usually Do Now
Our default approach today is basically what GitHub started doing in 2016: We inline our icons where they’re meant to display.
How we do that depends on the project. In an environment with few configuration options, we might treat SVG icons as we would any other partial or include. But ideally we’re able to define an SVG inline component/helper/macro of some sort, like the one we made for Handlebars. That way we can write code like this:
{{svg "path/to/icons/back.svg" class="Icon"}}
Code language: Handlebars (handlebars)
And have it magically include the SVG contents, with unnecessary XML attributes stripped and contextual attributes appended:
<svg class="Icon" viewBox="0 0 24 24" height="24" width="24">
<path d="M22 10H6.83l3.59-3.59a2 2 0 0 0-2.83-2.82l-7 7a2 2 0 0 0 0 2.83l7 7a2 2 0 0 0 2.83-2.83L6.83 14H22a2 2 0 0 0 0-4z"></path>
</svg>
Code language: HTML, XML (xml)
Building an SVG inliner into our templates also lets us customize fallback text and other features on a case-by-case basis, taking into account context and adjacent text:
{{#svg "path/to/icons/back.svg" aria-labelledby="back-title"}}
<title id="back-title">Go back</title>
{{/svg}}
Code language: Handlebars (handlebars)
The biggest drawback of this approach is that repetitive icon use can lead to duplicate markup in the page. But as long as server-side compression is enabled, icon markup is kept concise and you aren’t displaying thousands per page, the simplicity, compatibility and flexibility gains seem worth it.
It’s Okay to Do Things Differently
In our work with Champion Power Equipment, we inlined most icons. But the project’s dynamic file type icons are just a standard partial, and the more complex third-party set of country flags use <img>
elements so they can be cached.
Just as <img>
and background-image
serve different purposes, every SVG technique has its pros and cons. As long as you prioritize your users first and maintainers second while evaluating options, you’ll be just fine!