SVG `use` with External Reference, Take 2

Share this:

SVG has a <use> element which essentially means: go find the chunk of SVG that has this #identifier and kinda clone it and put it right here. It's an essential ingredient to an SVG icon system. There are some things to know about this that we haven't covered before.

The beauty of an external reference is that it leverages browser cache well.

Also that it's simple to work with. You put the correct file path to the file (the "SVG sprite") and reference an identifier, and it just works. You can have your server set to serve that file with all the correct headers such that the browser will hang onto it like it would any other asset you want to cache.

The problem was IE, but it's being fixed.

Most browsers that supported inline SVG supported this, so it was almost usable, with the major exception of IE. But they have now fixed that in Microsoft Edge. Edge doesn't have massive market share yet, but it is the future on Windows, so eventually, we'll be able to start using this without any extra work.

Because of the lack of perfect support, there are two major workaround choices.

Include the SVG sprite in all the HTML documents themselves. Works great. Tends to be very fast, but it bloats the page cache. And to get the best support, you need to include it at the top of the document, which means slightly delayed rendering for likely more important content. Or...

Ajax for the sprite. Which then can leverage browser caching. It can be tricky to do this though without a bit of FONI though (Flash of No Icons).

Once we can start using external references directly, it's not the same

This concept eluded me and is why I wanted to write this article.

I thought the external reference was the ultimate solution because it could do everything inline SVG referencing SVG in the same document could do. But unfortunately, it can't. SVG references this way has it's own separate DOM. It goes beyond the regular Shadow DOM boundary that all <use> is subject to.

With this:

<svg class="icon-1">
<use xlink:href="#icon-1"></use>
</svg>

You could write CSS (in the same stylesheet you use for the rest of the site) to color it:

/* This works.
It will cascade this fill through the shapes,
as long as there are no presentational fill
attributes on the shapes themselves. */
.icon-1 {
fill: red;
}

You can still do this with an externally referenced <use>, actually. But you can't style individual shapes like you could before.

/* You could reach individual shapes
to style because they share the same DOM.
But this WON'T WORK with externally referenced SVG. */
.path-1 {
fill: yellow;
}
/* This won't work either way,
because it crosses a shadow DOM boundary */
.icon-1 /* ~shadow~ */ .path-1 {
fill: yellow;
}

You can't get your hands on the internal shapes at all when you externally reference. For example, from the HTML document:

Just a reminder for your readers : when using <use> you can still color your SVG with two parameters by using fill and color css properties, and by setting the current-color value in your SVG. You said I better than me here https://css-tricks.com/cascading-svg-fill-color/

I am working on a project with this technique right now and it suits my needs pretty well (all my icons are in max 3 colors so I use fill, color, and background-color on the SVG’s wrapper via transparent areas in the SVG.)

I found a solution to have an external sprite sheet, using use and symbol, and being able to style each element inside the symbol with normal CSS, as if it were inline. The best part is that it works across all the browsers and you’re probably already using the right library.

svg4everybody({
polyfill: true
});

What svg4everybody does is replace the use tags with the actual symbol’s content—but only in IE and old Safari. By setting polyfill:true you force this behavior on all browsers.

So presumably, when we want to stop using svgforeverybody because Old IE no longer needs to be supported, somebody could extract this functionality into a smaller polyfill which we could all use? It’s probably beyond my ability to scan the svg4everybody code and find out where the magic is, but I’m sure there’s somebody clever out there that could make a small polyfill which just helps us out with this?

Even better: then each instance is completely separate, so you could style even multiple shapes inside differently. It’s just at the cost of 1) an extra (small) library 2) at least one XHR request (does it do one per icon or one total?) 3) dom weight

Jon: I don’t think there’s anything to extract; svg4everybody does exactly that thing, but by default it only does it as a “polyfill”. The only improvement that could be done would be removing the UA check.

That’s exactly what I mean. It’s a different DOM. But you can still SEE that DOM, like if you web inspect it. So it LOOKS just like a regular <use>, with the Shadow DOM and all, but it plays by different rules.

In Firefox, the <use> implementation is a bit old, predates the Shadow DOM specs quite a bit, and lets you select the “copied” elements. Which is a bug in itself, and it can also create bugs in your own code because if you do svg { fill: red; } it will apply to the inner SVG element as well, and when you later try to change the color of a specific icon with e.g. .cool-icon { fill: blue; } the icon will stay red.
In Chrome, you can use the shadow-piercing combinator like this: svg /deep/ path { fill: green; }, but this experimental feature is deprecated and is supposed to get removed in the future (following a decision at the latest Web Components face-to-face meeting).

I’ve dealt with this problem a few weeks ago. I’ve suggested to one of my company’s clients an icon refactor that would replace old png sprites per svg since we need a lot of customization.

External reference was on my mind since Chris’ first article about it, but when i realized that there were some multi part icons in the system that couldn’t be individually styled with this technique i went panic.

Fortunately i’ve found an article by Osvaldas Valutis that uses localStorage to cache the sprite and inject it inline. If localStorage is full, it loads and injects the sprite anyway. That’s how i’ve dealt with it, although this technique is not perfect and it slows down the rendering of icons by a fraction of time.

I will definitely try Federico Brigante technique that uses svg4everybody little trick and see which one performs better!

As Anselm Hannemann notes above, there is not currently any way to enable CORS content in SVG. It’s a planned addition to the spec, but not reliably supported in browsers.
Although the SVG 1.1 spec would suggest that styles set in CSS in the external file should still apply to the cloned content, in practice that was very problematic. SVG 2, leaving it up to browsers whether or not they process any stylesheet blocks in the external document, and therefore warns against authors relying on any <style> rules in the external file. Presentation attributes and inline style attributes should work as normal.
There are also some issues with determining the values of percentages for elements in an external file, because that file doesn’t have a viewport to create the reference 100% width/height. However, that’s never an issue when using <symbol>, because the symbol creates its own reference frame for the percentages.
Duplicated content, whether from the same or external file, will be stylable with inherited CSS custom properties (aka CSS variables). See blog post with demos that already work in Firefox. The SVG parameters you would specify in a URL function would map to CSS custom properties inherited by the root element. For <use> elements, you’d just set them on the <use> with CSS, and the duplicated content would inherit them.

If one of the symbols links to the gradient from within the same file, and this file is used as an external reference, then the gradient won’t be shown in Chrome, the same applies to any other references (e.g. filters)

👋

CSS-Tricks* is created, written by, and maintained by Chris Coyier and a team of swell people. It is built on WordPress and powered up by Jetpack. It is made possible through sponsorships from products and services we like.