A Pretty Good SVG Icon System

Avatar of Chris Coyier
Chris Coyier on (Updated on )

I’ve long advocated SVG icon systems. Still do. To name a few benefits: vector-based icons look great in a high pixel density world, SVG offers lots of design control, and they are predictable and performant.

I’ve also often advocated for a SVG icon system that is based on <symbol>s (an “SVG sprite”) and the <use> element for placing them. I’ve changed my mind a little. I don’t think that is a bad way to go, really, but there is certainly a simpler (and perhaps a little better) way to go.

Just include the icons inline.

That’s it. Sorry if you were hoping for something fancier.

Like this:

<button>
  <svg class="icon icon-cart" viewBox="0 0 100 100" aria-hidden="true">
    <!-- all your hot svg action, like: -->
    <path d=" ... " />
  </svg>
  Add to Cart
</button>

Or perhaps more practically, with your server-side include of choice:

<button>
  <?php include("/icons/icon-cart.svg"); ?>
  Add to Cart
</button>

Like I said:

Advantage #1: No Build Process

You need no fancy tooling to make this work. Your folder full of SVG icons remain a folder full of SVG icons. You’ll probably want to optimize them, but that’s about it.

Advantage #2: No Shadow DOM Weirdness

SVG icons included as a <use> reference have a shadow DOM boundary.

Showing the Shadow DOM boundary in Chrome DevTools

This can easily cause confusion. For example:

var playButton = document.querySelector("#play-button-shape");

playButton.addEventListener("click", function() {
  alert("test");
});

That’s not going to work. You’d be targeting the path in the <symbol>, which doesn’t really do anything, and the click handler is kinda lost in the cloning. You’d have to attach a handler like that to the parent <svg>, like #play-button.

Likewise, a CSS selector like:

.button #play-button-shape {

}

Will not select anything, as there is a Shadow DOM boundary between those two things.

When you just drop inline SVG right into place, there is no Shadow DOM boundary.

Advantage #3: Only the Icons You Need

With a <use>/<symbol> system, you have this SVG sprite that is likely included on every page, whether or not they are all used on any given page or not. When you just include inline SVG, the only icons on the page are the ones you are actually using.

I listed that as an advantage, but it sorta could go either way. To be fair, it’s possible to cache an SVG sprite (e.g. Ajax for it and inject onto page), which could be pretty efficient.

That’s a bit of a trick question. <use> itself doesn’t have anything to do with caching, it’s about where the SVG is that the <use> is referencing. If the sprite is Ajax’d for, it could be cached. If the sprite is just part of the HTML already, that HTML can be cached. Or the <use> can point to an external file, and that can be cached. That’s pretty tempting, but…

Advantage #4: No cross-browser support concerns

No IE or Edge browser can do this:

<use xlink:href="/icons/sprite.svg#icon-cart" />

That is, link to the icon via a relative file path. The only way it works in Microsoft land is to reference an ID to SVG on the same page. There are work around’s for this, such as Ajaxing for the sprite and dumping it onto the page, or libraries like SVG for Everybody that detects browser support and Ajaxs for the bit of SVG it needs and injects it if necessary.

Minor Potential Downside: Bloat of HTML Cache

If you end up going the sprite route, as I said, it’s tempting to want to link to the sprite with a relative path to take advantage of caching. But Microsoft browsers kill that, so you have the choice between:

  1. A JavaScript solution, like Ajaxing for the whole sprite and injecting it, or a polyfill.
  2. Dumping the sprite into the HTML server-side.

I find myself doing #2 more often, because #1 ends up with async loading icons and that feels janky. But going with #2 means “bloated” HTML cache, meaning that you have this sprite being cached over and over and over on each unique HTML page, which isn’t very efficient.

The same can be said for directly inlining SVG.


Conclusion and TLDR: Because of the simplicity, advantages, and only minor downsides, I suspect directly inlining SVG icons will become the most popular way of handling an SVG icon system.