From our sponsor: Meco is a distraction-free space for reading and discovering newsletters, separate from the inbox.
One of the most common use cases for SVG graphics is icon systems, and one of the most commonly-used SVG spriting techniques is one that uses the SVG <use>
element to “instantiate” the icons anywhere they are needed in a document.
Instantiating icons or any other SVG element or image using the <use>
element introduces some challenges when it comes to styling the instance of that element. The aim of this article is to give you an overview of some of the possible ways to work around the styling limitations introduced by <use>
.
But before we get started, let’s take a quick look into SVG’s main structuring and grouping elements that will gradually take us into the world of use
, the shadow of its DOM, and then bring us back into the light with CSS. We’ll go over why styling <use>
content can be challenging and ways to work around that.
Quick Overview of SVG Structuring, Grouping and Referencing (Re-using) Elements in SVG
There are four main elements in SVG that are used to define, structure and reference SVG code within the document. These elements make reusing elements easy, while maintaining clean and readable code. Because of the nature of SVG, some of these elements have the same functionality as certain commands in graphics editors.
The four main SVG grouping and referencing elements are: <g>
, <defs>
, <use>
and <symbol>
.
The <g>
element (short for “group”) is used for logically grouping together sets of related graphical elements. In terms of graphics editors, such as Adobe Illustrator, the <g>
element serves a similar functionality as the Group Objects function. You can also think of a group as being similar to the concept of a layer in a graphics editor, since a layer is also a grouping of elements.
Grouping elements together is useful for when you want to apply a style and have that style be inherited by all the elements in the group, and is particularly useful for when you want to animate a group of elements while maintaining their spatial relationships with each other.
The <defs>
element is used to define elements that you want to reuse later. Defining elements with <defs>
is useful for when you want to create sort of a “template” that you want to use multiple times throughout the document. Elements defined inside a <defs>
element are not rendered on the canvas except if you “call” them somewhere in the document.
<defs>
is useful for defining many things, but one of the main use cases is defining patterns like gradients, for example, and then using those gradients as stroke fills on other SVG elements. It can be used to define any elements that you want to render anywhere on the canvas by reference.
The <symbol>
element combines the benefits of both the <defs>
and the <g>
elements in that it is used to group together elements that define a template that is going to be referenced elsewhere in the document. Unlike <defs>
, <symbol>
is not usually used to define patterns, but is mostly used to define symbols such as icons, for example, that are referenced throughout the document.
The <symbol>
element has an important advantage over the other two elements: it accepts a viewBox
attribute which allows it to scale-to-fit inside any viewport it is rendered in.
The <use>
element is the element you use to reference any element defined elsewhere in the document. It lets you reuse existing elements, giving you a similar functionality to the copy-paste functionality in a graphics editor. It can be used to reuse a single element, or a group of elements defined with the <g>
element, the <defs>
element, or a <symbol>
.
To use an element you pass a reference to that element—an ID—inside the use
‘s xlink:href
attribute, and you position that element using x
and y
attributes. You can apply styles to the use
element and those styles will cascade into the contents of the use
element.
But what is the content of <use>
? Where is it cloned? And how does the CSS cascade work with that content?
Before we answer these questions, and since we only covered a quick overview of SVG’s structuring and grouping elements, it’s worth mentioning a couple of articles to learn more about these elements and about the viewBox
attribute used with <symbol>
:
- Structuring, Grouping, and Referencing in SVG — The
<g>
,<use>
,<defs>
and<symbol>
Elements - Understanding SVG Coordinate Systems (Part 1): The Viewport,
viewBox
andPreserveAspectRatio
SVG <use> and The Shadow DOM
When you reference an element with <use>
, the code might look something like this:
<symbol id="my-icon" viewBox="0 0 30 30"> <!-- icon content / shapes here --> </symbol> <use xlink:href="#my-icon" x="100" y="300" />
What is rendered on the screen is the icon whose content is defined inside <symbol>
, but it’s not that content that got rendered, but rather the content of the <use>
, which is a duplicate—or clone—of the <symbol>
’s content.
But the <use>
element is only one element and it is self-closing—there is no content somewhere between an opening and closing use
tag, so where has the <symbol>
content been cloned?
The answer to that is: the Shadow DOM. (Somehow the Shadow DOM always reminds me of the Batman. I don’t know why.)
What is the Shadow DOM?
The Shadow DOM is similar to the normal DOM except that, instead of being part of the main document subtree, nodes in the Shadow DOM belong to a document fragment which is basically just another subtree of nodes which are not as vulnerable to scripts and styles as normal DOM nodes are. This gives authors a way to encapsulate and scope styles and scripts when creating modular components. If you’ve ever used the HTML5 video
element or the range input type and wondered where the video controls or range slider components came from, then you’ve already come across the Shadow DOM before.
In the case of the SVG <use>
element, the contents of the referenced element are cloned into a document fragment that is “hosted” by <use>
. <use>
, in this case, is called a Shadow Host.
So, the contents of <use>
(the clone or copy of whatever element it is referencing) are present inside a shadow document fragment.
In other words, they are there, but they are not visible. They are just like normal DOM content, but instead of being available in the “high-level” DOM which is accessible by CSS selectors and JavaScript in the main document, they are copied into a document fragment that is hosted by <use>
.
Now, if you’re a designer, you might be thinking: “OK, I get it but is there a way to inspect that sub-document to actually see into its contents?” The answer is: Yes! You can preview the contents of the shadow DOM using Chrome’s developer tools. (Inspecting the contents of a shadow DOM is currently not possible in Firefox.) But in order to do that, you need to first enable shadow DOM inspection in the “General” tab inside the Settings panel that can be opened by clicking on the Cog icon. You can learn more about how to do that here.
Once you’ve enable shadow DOM inspection in the dev tools, you can see the cloned elements in the Elements panel, just like you would with normal DOM elements. The following image shows an example of a <use>
element referencing the contents of a <symbol>
. Notice the “#shadow-root” and the contents of that fragment when expanded—they are a copy of the contents of the <symbol>
.
Looking at the inspected code, you can see that the shadow DOM is pretty much the same as the normal DOM, except that is has different characteristics when it comes to handling with CSS and JavaScript from the main document. There are also other differences between them, but the shadow DOM cannot possibly be covered in this section because it is too big of a concept, so if you want to read and learn more about it, I recommend the following articles:
For me, and considering how limited my interaction with the shadow DOM is, I think of it as being just like the normal DOM, except that it needs to be handled differently when it comes to accessing its elements for styling with CSS (and JavaScript). This is what matters to us as SVG developers: how the existence of the contents of <use>
inside a shadow DOM affects that content when it comes to applying or changing styles, because we want to be able to style them. The whole point of using <use>
is being able to create different “copies” of an element, and in a lot of cases, what we want is to be able to style each copy differently. For example, think about a logo with two styles (inverted color themes) or multi-coloured icons of which each has its own theme. So, it would only make sense for us to expect to be able to do that using CSS.
That being said, we mentioned earlier that the contents of the shadow DOM are not vulnerable to CSS like the normal DOM is. So how do we style its content? We cannot target a path descendant of <use>
like this:
use path#line { stroke: #009966; }
because we cannot access the shadow DOM using regular CSS selectors.
There are a couple of special selectors that allow you to penetrate the shadow DOM boundaries to apply styles to nodes inside of it, but those selectors are not only not well supported, but they are limited compared to the long list of selectors provided in CSS for selecting and styling regular DOM elements.
Moreover, we want a simpler way to style the contents of an SVG <use>
without having to get our hands dirty with shadow DOM specifics—using simple CSS and SVG only.
In order to achieve that and get a little more control over styling the contents of <use>
, we need to think from a different angle, and start by leveraging the CSS cascade and taking advantage of its inheritance capabilities.
Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.
The Style Cascade
Since SVG elements can be styled using CSS using one of three different ways: external CSS styles (in an external style sheet), internal style blocks (<style>
elements) and inline styles (in a style
attribute), it only makes sense that the cascade governs how these styles are applied to these elements.
In addition to CSS properties, SVG elements can also be styled using presentation attributes. Presentation attributes are a shorthand for setting a CSS property on an element. Think of them as special style properties. Their nature is what makes them contribute to the style cascade, but maybe in a less expected way.
In the following code snippet which simply displays a pink circle with a yellow stroke, stroke
, stroke-width
, and fill
are all presentation attributes.
<svg viewBox="0 0 100 100"> <circle fill="deepPink" stroke="yellow" stroke-width="5" cx="50" cy="50" r="10"></circle> </svg>
In SVG, a subset of all CSS properties may be set by SVG attributes, and vice versa. This means that not all CSS properties can be specified on an SVG element as presentation attributes, and not all presentation attributes available in SVG (there are a lot of them!) can be specified in CSS.
The SVG specification lists the SVG attributes that may be set as CSS properties. Some of these attributes are shared with CSS (already available as CSS properties), such as opacity
and transform
, among others, while some are not, such as fill
, stroke
and stroke-width
, among others.
In SVG 2, this list will include x
, y
, width
, height
, cx
, cy
and a few other presentation attributes that were not possible to set via CSS in SVG 1.1. The new list of attributes can be found in the SVG 2 specification.
If you’re like me, then you would expect presentation attributes to have a higher specificity than all other style declarations. I mean, after all, external styles are overridden by internal styles in style blocks, and style block declarations are overridden by inline styles in a style attribute.. so it seems as though the more “internal” the styles get, the more specificity they have, and so when a property gets its own attribute, it makes it more powerful and thus it would override all other style declarations. Although that still makes sense to me, this is not how it really works.
As a matter of fact, presentation attributes count as low-level “author style sheets” and are overridden by any other style definitions: external style sheets, document style sheets and inline styles. The only power presentation attributes have in the style cascade is over inherited styles. That is, presentation attributes can only override inherited styles on an element, and are overridden by any other style declaration.
Great, now that that’s been cleared up, let’s get back to the <use>
element and its content.
We now know that we cannot set styles on the elements inside <use>
using CSS selectors.
We also know that, just like with the <g>
element, styles you apply to <use>
will be inherited by all its descendants (those in the shadow DOM).
So a first attempt to change the fill
color of an element inside <use>
would be to apply that fill color to the <use>
element itself and let inheritance and the cascade do its thing.
However, that brings up two issues:
- The fill color will be inherited by all the descendants of
<use>
, even those you may not want to style. (If you have only one element inside<use>
, then this won’t be an issue.) - If you’ve exported an SVG from a graphics editor and/or got an SVG from a designer who did that and for any reason you can’t touch the SVG code, then you’re likely to end up with SVG elements with presentation attributes applied (unless you explicitly specified that you don’t want this to happen upon exporting the SVG, but that’s another topic), and the values of these attributes are going to override any styles you apply on
<use>
. Now, I’m assuming that if you are specifying styles on<use>
then you want those styles to be inherited by its descendants, so presentation attributes would be causing an inconvenience in this case.
And even if you do have access to the SVG code and you can get rid of the presentation attributes, I highly recommend against that because:
- Removing the attributes used to set certain properties will reset those properties to their initial browser-default values—which, in most cases, is all black fills and strokes (if we’re talking about colors, for example).
- By resetting all values you force yourself into specifying styles for all the properties set, so unless you want to do just that, then you don’t want to get rid of those presentation attributes.
- The presentation attributes that are initially available are a great fallback mechanism for when the external styles you set are not applied for any reason. If the CSS fails to load because something was messed up, your icon will at least have some default nice styles to fall back to. I highly recommend keeping them.
OK so now we have those attributes but we also want to style different instances of our, say, icon, differently.
The way to do that is to make sure we force the presentation attributes into inheriting the styles set on <use>
or find a way to work around them overriding those values. And in order to do that, we need to take advantage of the CSS cascade.
Let’s start with the simplest of examples and gradually move on to more complex scenarios.
Overriding Presentation Attribute Values From CSS
Presentation attributes are overridden by any other style declaration. We can take advantage of this and have an external style declaration force the presentation attribute into overriding its value with the inherited value from use
.
By using the CSS inherit
keyword, this becomes quite simple. Take a look at the following example where we have an ice cream icon made up of just one path whose fill color we want to change for different instances. The icon is Created by Erin Agnoli from the Noun Project.
<svg> <symbol id="ic"> <path fill="#000" d="M81,40.933c0-4.25-3-7.811-6.996-8.673c-0.922-5.312-3.588-10.178-7.623-13.844 c-2.459-2.239-5.326-3.913-8.408-4.981c-0.797-3.676-4.066-6.437-7.979-6.437c-3.908,0-7.184,2.764-7.979,6.442 c-3.078,1.065-5.939,2.741-8.396,4.977c-4.035,3.666-6.701,8.531-7.623,13.844C22.002,33.123,19,36.682,19,40.933 c0,2.617,1.145,4.965,2.957,6.589c0.047,0.195,0.119,0.389,0.225,0.568l26.004,43.873c0.383,0.646,1.072,1.04,1.824,1.04 c0.748,0,1.439-0.395,1.824-1.04L77.82,48.089c0.105-0.179,0.178-0.373,0.225-0.568C79.855,45.897,81,43.549,81,40.933z M49.994,11.235c2.164,0,3.928,1.762,3.928,3.93c0,2.165-1.764,3.929-3.928,3.929s-3.928-1.764-3.928-3.929 C46.066,12.997,47.83,11.235,49.994,11.235z M27.842,36.301c0.014,0,0.027,0,0.031,0c1.086,0,1.998-0.817,2.115-1.907 c0.762-7.592,5.641-13.791,12.303-16.535c1.119,3.184,4.146,5.475,7.703,5.475c3.561,0,6.588-2.293,7.707-5.48 c6.664,2.742,11.547,8.944,12.312,16.54c0.115,1.092,1.037,1.929,2.143,1.907c2.541,0.013,4.604,2.087,4.604,4.631 c0,1.684-0.914,3.148-2.266,3.958H25.508c-1.354-0.809-2.268-2.273-2.268-3.958C23.24,38.389,25.303,36.316,27.842,36.301z M50.01,86.723L27.73,49.13h44.541L50.01,86.723z"/> </symbol> </svg>
The ice cream icon’s contents (the path
) are defined in a <symbol>
element, which means that they won’t be directly rendered on the SVG canvas.
Then, we render multiple instances of the icon using <use>
.
<svg class="icon" viewBox="0 0 100 125"> <use class="ic-1" xlink:href="#ic" x="0" y="0" /> </svg> <svg class="icon" viewBox="0 0 100 125"> <use class="ic-2" xlink:href="#ic" x="0" y="0" /> </svg>
And we set the width and height of the icons from CSS. I am using the same dimensions as the viewBox
dimensions but they don’t have to be identical. However, to avoid getting any excess white space inside the SVG, make sure you maintain the same aspect ratio between them.
.icon { width: 100px; height: 125px; }
Using the above code, you get the following result:
Note that I have added a black border to the SVGs so you can see the boundaries of each one and to show you that the contents of the first SVG where we defined the icon contents are not rendered. This is to make a point here: the SVG document where you define your symbol
s will still be rendered on the page, even if it contains no rendered shapes. In order to avoid that, make sure you set display: none
on the first SVG. If you don’t hide the SVG containing the icon definitions, it will be rendered even if you don’t explicitly set any dimensions for it—the browser will default to 300 pixels by 150 pixels, which is the default size for non-replaced elements in CSS, so you will end up with a white area on the page that you do not want.
Now let’s try to change the fill color for each icon instance:
use.ic-1 { fill: skyblue; } use.ic-2 { fill: #FDC646; }
The fill color of the icons still does not change because the inherited color values are being overridden by the fill="#000"
on the path
element. To prevent that from happening, let’s force the path
into inheriting the color value:
svg path { fill: inherit; }
And voila!—the colors we set on the <use>
elements are now applied to the path
in each one. Check the live demo out and play with the values creating more instances and changing their colors as you go:
See the Pen f15cec4a61e753259bd768de2e20500b by Sara Soueidan (@SaraSoueidan) on CodePen.
Now this technique is useful when you want to force the contents of <use>
to inherit the styles you set on it. But in most cases, this may not be exactly what you want. There are other styling scenarios, so we’ll go over some of them next.
Styling <use> Content with the CSS all
Property
A while back I was working on an icon referenced with use
and I wanted one of the elements inside of it to inherit all the styles I set on <use>
like fill
, stroke
, stroke-width
, opacity
and even transform
. Basically, I wanted to be able to control all of those attributes from CSS while also keeping the presentation attributes in the markup as fallback.
If you find yourself in such a scenario as well, you will probably find it time-consuming to have to do this in the CSS:
path#myPath { fill: inherit; stroke: inherit; stroke-width: inherit; transform: inherit; /* ... */ }
Looking at the above snippet, you can see a pattern and it would only make sense for us to be able to combine all of those properties into one property and set that property’s value to inherit
.
Fortunately, this is where the CSS all
property helps. I have written about using the all
property to style SVG <use>
content in property’s CSS Reference entry, but it is worth a second look here since we’re in the right context.
Using the all
property, we can do this:
path#myPath { all: inherit; }
This works great in all browsers that support the all
property (see property’s entry for details), however there is something important to keep in mind: this declaration will set literally all of the properties on the element to inherit their values from their ancestor, even those you might not have wanted to target. So unless you want to style all the properties of your element from your CSS, then you do not want to use this—it is an extreme measure and is particularly useful for when you want to “bare-bone” your element and have complete control over its styling properties in CSS, which may not be too often. If you use this declaration and don’t specify values for all of the properties in your CSS, they will go up and cascade until they find a value to inherit, which in most cases will be the default browser styles from the default user agent style sheet.
Note that this will only affect the attributes that can be set in CSS, not the SVG-only attributes. So if an attribute can be set as a CSS property, it will be set to inherit
, otherwise it won’t.
Being able to force the presentation attributes to inherit from <use>
styles is powerful, but what if you have an icon with multiple elements and you don’t want all of those elements to inherit the same fill
color from use
? What if you want to apply multiple different fill colors to different use
descendants? Setting one style on use
no longer suffices. We need something else to help us cascade the right colors to the right elements..
Using the CSS currentColor
Variable For Styling <use>
Content
Using the CSS currentColor
variable in conjunction with the above technique, we can specify two different colors on an element, instead of just one. Fabrice Weinberg wrote about this technique on his Codepen blog a little less than a year ago.
The idea behind this technique is to use both the fill
and the color
properties on <use>
, and then have these colors cascade into the contents of <use>
by taking advantage of the variable nature of currentColor
. Let’s jump right into a code example to see how this works.
Suppose we want to style this minimal Codrops logo using two colors—one for the front drop and one for the back drop—for every instance of that logo.
First, let’s start with the code for the above screenshot: we have the symbol
containing our icon definition and then three <use>
instances creating the three logo instances.
<svg style="display: none;"> <symbol id="codrops" viewBox="0 0 23 30"> <path class="back" fill="#aaa" d="M22.63,18.261c-0.398-3.044-2.608-6.61-4.072-9.359c-1.74-3.271-3.492-5.994-5.089-8.62l0,0 c-1.599,2.623-3.75,6.117-5.487,9.385c0.391,0.718,0.495,1.011,0.889,1.816c0.143,0.294,0.535,1.111,0.696,1.43 c0.062-0.114,0.582-1.052,0.643-1.162c0.278-0.506,0.54-0.981,0.791-1.451c0.823-1.547,1.649-2.971,2.469-4.33 c0.817,1.359,1.646,2.783,2.468,4.33c0.249,0.47,0.513,0.946,0.791,1.453c1.203,2.187,2.698,4.906,2.96,6.895 c0.292,2.237-0.259,4.312-1.556,5.839c-1.171,1.376-2.824,2.179-4.663,2.263c-1.841-0.084-3.493-0.887-4.665-2.263 c-0.16-0.192-0.311-0.391-0.448-0.599c-0.543,0.221-1.127,0.346-1.735,0.365c-0.56-0.019-1.095-0.127-1.599-0.313 c1.448,3.406,4.667,5.66,8.447,5.78C19.086,29.537,23.469,24.645,22.63,18.261z"/> <path class="front" fill="#ddd" d="M6.177,11.659c0.212,0.367,0.424,0.747,0.635,1.136c0.164,0.303,0.333,0.606,0.512,0.927 c0.683,1.225,1.618,2.898,1.755,3.937c0.144,1.073-0.111,2.056-0.716,2.769c-0.543,0.641-1.315,1.014-2.186,1.067 c-0.87-0.054-1.643-0.43-2.186-1.067c-0.604-0.713-0.858-1.695-0.715-2.771c0.137-1.036,1.072-2.712,1.755-3.936 c0.18-0.32,0.349-0.623,0.513-0.927C5.752,12.404,5.964,12.026,6.177,11.659 M6.177,5.966L6.177,5.966 c-1.02,1.649-2.138,3.363-3.247,5.419c-0.932,1.728-2.344,3.967-2.598,5.88c-0.535,4.014,2.261,7.09,5.846,7.203 c3.583-0.113,6.379-3.189,5.845-7.203c-0.255-1.912-1.666-4.152-2.598-5.88C8.314,9.329,7.196,7.617,6.177,5.966L6.177,5.966z"/> </symbol> </svg> <svg height="90px" width="69px"> <use xlink:href="#codrops" class="codrops-1"/> </svg> <svg height="90px" width="69px"> <use xlink:href="#codrops" class="codrops-2"/> </svg> <svg height="90px" width="69px"> <use xlink:href="#codrops" class="codrops-3"/> </svg>
If we were to set the fill
color on the <use>
element for each instance, that color would be inherited by both paths (drops) and they would both end up having the same color—which is not what we want.
So instead of specifying only the fill
color and having it cascade the default way, we are going to use the currentColor
variable to make sure the small drop in the front gets a different color value: the value specified using the color
property.
First, we need to insert the currentColor
where we want that color value to be applied; it goes into the markup where the icon contents are defined, inside the <symbol>
. So the code looks like this:
<svg style="display: none;"> <symbol id="codrops" viewBox="0 0 23 30"> <path class="back" fill="#aaa" d="..."/> <path class="front" fill="currentColor" d="..."/> </symbol> </svg>
Next we need to remove the fill
presentation attribute from the other drop, and let it inherit the fill
color from use
without using the inherit
technique.
If we were to use the inherit
keyword to force the presentation attributes into inheriting their values from use
, both paths will inherit the same value and the currentColor
will no longer have an effect. So, in this technique, we do need to remove the attribute we want to set in CSS, and only keep the one whose value we want to set using currentColor
.
<svg style="display: none;"> <symbol id="codrops" viewBox="0 0 23 30"> <path class="back" d="..."/> <path class="front" fill="currentColor" d="..."/> </symbol> </svg>
Now, using fill
and color
properties on <use>
, we style the drops:
.codrops-1 { fill: #4BC0A5; color: #A4DFD1; } .codrops-2 { fill: #0099CC; color: #7FCBE5; } .codrops-3 { fill: #5F5EC0; color: #AEAFDD; }
Each <use>
element gets its own fill
and color
values. For each one, the fill
color cascades and is inherited by the first path which does not have a fill
attribute, and the value of the color
property is used as a value for the fill
attribute on the second path.
So what happened here is that the current color value was leaked into the innards of the <use>
element, using the currentColor
variable. Pretty neat, right?
Here is the live demo for the above code:
See the Pen 9cb698c206c6b572264f9b0509b0156f by Sara Soueidan (@SaraSoueidan) on CodePen.
This two-color variation technique is quite useful for simple bicoloured logos. In Fabrice’s article, he created three different variations of the Sass logo by changing the color of the text versus that of the background.
The currentColor
keyword is the only available CSS variable in CSS today. However, if we had more variables, wouldn’t it make it possible for us to distribute and leak even more values into the <use>
content? Yes, it would. Amelia Bellamy-Royds introduced this very concept in a Codepen blog post a little less than a year ago as well. Let’s take a look at how that works.
The Future: Styling <use> Content with CSS Custom Properties a.k.a CSS Variables
Using CSS Custom Properties (a.k.a CSS Variables), you can style the contents of <use>
without having to force the browser into overriding any presentation attribute values.
As defined on MDN, CSS Variables are entities defined by authors, or users, of Web pages to contain specific values throughout a document. They are set using custom properties and are accessed using a specific functional notation var()
. They are very similar to CSS preprocessor (like Sass) variables, but are more flexible and can do things preprocessor variables can’t. (An entry on CSS Variables will soon be added to the Codrops CSS Reference, so stay tuned.)
Variables, be it CSS variables or preprocessor variables, can have many usage examples, but theming (colors) is one of the most common use cases. And in this section we’ll go over how that can be done when styling SVGs.
We’ll start with one image defined in a symbol
and instantiated with use
and apply this technique to one image only; the concepts applied to style the contents of <use>
in this example can be applied to as many <use>
elements as you want.
So, suppose we have the following cute hipster robot illustration designed by Freepik.
The code for the robot contains the colors that make it up.
<svg style="display: none"> <symbol id="robot" viewBox="0 0 340 536"> <path d="..." fill="#fff" /> <path d="..." fill="#D1312C" /> <path d="..." fill="#1E8F90" /> <path d="..." fill="#1E8F90" /> <path d="..." fill="#fff" /> <path d="..." fill="#6A4933" /> <path d="..." fill="#1E8F90" /> <path d="..." fill="#6A4933" /> <path d="..." fill="#fff" /> <path d="..." fill="#6A4933" /> <path d="..." fill="#F2B42B" /> <path d="..." fill="#fff" /> <!-- rest of the shapes --> </symbol> </svg>
Now, we are not going to use the CSS Variables as values for the fill
attribute of each path; instead, we’re going to use them as fill color values using the CSS fill
property, and we’re going to keep the fill
attributes in place. The attributes will be used as a fallback for browsers that don’t support CSS Variables, so the image will still look as it initially did if the variables fail to work in those browsers.
With the variables added, the above code will look like so:
<svg style="display: none"> <symbol id="robot" viewBox="0 0 340 536"> <path d="..." fill="#fff" /> <path d="..." fill="#D1312C" /> <path d="..." fill="#1E8F90" style="fill: var(--primary-color)" /> <path d="..." fill="#1E8F90" style="fill: var(--primary-color)" /> <path d="..." fill="#fff" /> <path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" /> <path d="..." fill="#1E8F90" style="fill: var(--primary-color)" /> <path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" /> <path d="..." fill="#fff" /> <path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" /> <path d="..." fill="#F2B42B" style="fill: var(--secondary-color)" /> <path d="..." fill="#fff" /> <!-- rest of the shapes --> </symbol> </svg>
Since inline style
tags override presentation attributes, browsers that support CSS Variables will use those variables as fill colors for the shapes. Browsers that do not support CSS variables will use the fill
attribute values instead.
Next, we need to define the values for the variables in CSS. First, the illustration will be instantiated using use
:
<svg width="340" height="536"> <use xlink:href="#robot" id="robot-1" /> </svg>
Then, the variables will be defined on use
so that they cascade into its contents. The colors you choose for the variables will make up the color scheme of the contents of your illustration. So, for the above robot, there were three main colors making up the graphic, so I named them primary, secondary and tertiary.
#robot-1 { --primary-color: #0099CC; --secondary-color: #FFDF34; --tertiary-color: #333; }
You can still use the fill
and color
properties alongside these variables, but you may not need to or simply not want to. So, given the above colors defined for the variables, the robot now looks like this:
You can have as many copies of the image as you want, and for each use
define a set of different colors to be used, and end up with different themes. This is particularly useful for when you want to style a logo in different ways depending on the context, or for any other similar use cases.
Now, we mentioned that browsers that don’t support CSS Variables are going to fall back to the initial styles defined in the presentation attributes, and browsers that do support the variables will use the variables in the fill
properties to override the attributes. Great. But what happens if the browser does support CSS Variables but the author fails to provide them with a value for a specific variable or if the value they provided is invalid?
For our hipster robot here, we defined three variables, and only a few elements inside of the image did not get any variables because the colors used were complimentary and would go with pretty much any color theme used. So, if you display the above code in a browser that supports CSS variables (currently only Firefox) and remove the variable declarations from the CSS, you will end up with this:
If the values provided for the variables are either not set or invalid, the browser will default to its own colors, which is usually black for fill and stroke colors in SVG.
A way to avoid that is to provide another fallback color for supporting browsers. Indeed, the CSS variables syntax comes with a way to do just that: instead of providing only the variable name inside the var()
function as an argument, you provide two comma-separated arguments: the variable name and a fallback color value—which in this case is the value we have in the presentation attributes.
So, going over the above code for the robot, it will look like this:
<svg style="display: none"> <symbol id="robot" viewBox="0 0 340 536"> <path d="..." fill="#fff" /> <path d="..." fill="#D1312C" /> <path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" /> <path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" /> <path d="..." fill="#fff" /> <path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" /> <path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" /> <path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" /> <path d="..." fill="#fff" /> <path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" /> <path d="..." fill="#F2B42B" style="fill: var(--secondary-color, #F2B42B)" /> <path d="..." fill="#fff" /> <!-- rest of the shapes --> </symbol> </svg>
And that’s it. For any variable that fails to load its defined value or that does not have one, the browser will fall back to the initial color defined in the markup. Wonderful.
Using this technique, you can now reference the robot anywhere you want on the page with <use>
, and for every new instance define a set of variable values in the CSS, and you’ll have a different color theme per instance.
You can play with the above demo, create as many copies of the robot as you want and assign different variable values to them in this live demo, just make sure you use Firefox at this time because it’s the only browser supporting CSS Variables at the time of writing of this article:
See the Pen c30e270090b2460ba6e6833c611e5a76 by Sara Soueidan (@SaraSoueidan) on CodePen.
If you view the demo in Firefox, you will see the blue + yellow version of the robot that we defined with CSS variables. Make sure you check the demo in Chrome to see how it falls back to the initial colors (green version), and try removing the variable declarations from the CSS in Firefox to also see how the fallback works.
Summing Up
Phew. That was a lot.
By taking advantage of the CSS cascade, styling the contents of <use>
—though in a shadow DOM—can become less complicated. And with CSS variables (be it currentColor
alone or the custom properties) we can penetrate the lines of the shadow DOM and customize our graphics to our liking while also providing very good fallback for when anything goes wrong.
Personally, I am incredibly excited about the CSS Variables + SVG combination. I love how powerful they are together, especially given the great fallback mechanism we have with it. They are currently only supported in Firefox, as we mentioned, but if you want to see them get wider support you can start by voting for them in other browsers such as on the MS Edge User Voice forums.
We may even get other ways to style use
content in the future as well since there are already discussions going on about using CSS Variables as SVG parameters; so this article, though long, might have not covered everything there is to know about this topic. If you have any other ideas, please feel free to share them in the comments below.
Dealing with the contents of reuse
d SVG elements has been one of those SVG topics that many people seem to find some hardship with, because of the nature of how the cloned code behaves and where it is cloned to. There are a lot more topics to cover that are related to that, but those are topics for other articles.
I hope you enjoyed this article and found it useful. Thank you for reading.
Worth a mention in this context: Currently it’s not possible to load an SVG symbol via
use
from an external URL as no browser vendor currently supports CORS here. :/Hey Anselm
If I’m not mistaken, I think it’s the opposite: all browsers support it except IE. See: https://css-tricks.com/svg-use-external-source/ … Unless you’re referring to something else?
Edit: After reading Eric’s comment below I understand you mean referencing the SVG from a different domain (hence CORS). You’re right. Too bad it doesn’t yet work. :/
Yes, I meant from a different domain like a CDN – sorry for being unclear here. See i.e. the Chrome bug here: https://code.google.com/p/chromium/issues/detail?id=470601
There is a solution, by using a JS to load the SVG defs and insert them in the page. It works well, on all browers, and event from a CDN
I think Anselm means in terms of a URL that isn’t the current domain.
Won’t the Shadow Dom piercing selector allow you to do something like this `use >>> path { fill: #00ff00; }`
Oh I’m dumb, click through link points out this posibility ^_^.
great article on this subject, been wanting to try this for a while now
Great article, Sara.
One important note I have discovered recently, your SVG with your symbols in it, if you are using gradients of any kind in that SVG, they will not show up if that SVG is set to display: none. You instead need to say display: block; height: 0; width: 0 to still hide it effectively. I don’t know if this is a bug with SVG or if its intentional or what.
Hey jake
Thanks for your comment! That’s quite interesting! Do you have a live example of that that I could check out? Also, if you’re not sure about whether some behaviour is a bug or a feature, asking one of the spec editors is always a great way to confirm either!
Yup! Here you go:
SVG Sprite is not hidden in any way
http://codepen.io/jakobud/pen/jPpboq
SVG Sprite is set to display: none….. gradients do not show up!
http://codepen.io/jakobud/pen/ZGjbde
SVG Sprite is set to display: block, width: 0, height: 0… gradients work again
http://codepen.io/jakobud/pen/KpBdjM
I just discovered this 2 nights ago. I have no idea if this is part of the SVG spec or if this is a bug or what. It’s definitely doing it on the latest Chrome and FF. The funny thing is, I have searched and searched and not found ANYTHING about this written or blogged about by anyone.
In any case, it’s important to know about if you are using SVG sprite techniques. It took me many hours to figure out why my gradients were not showing up.
Also, it’s not just the SVG being display none that causes this problem, but if any parent element of the SVG is display none, the same will occur.
Hey Jake
Thanks for the examples! I will dig into them as soon as I can and of course ask someone whether this is a feature of bug and let you know in the comments here again.
Cheers!
yeh
RE: “I don’t know if this is a bug with SVG or if its intentional”
This is definitely a bug. Just an unfortunately common one! The SVG 1.1 spec says
There is similar wording for gradients, patterns, markers, defs, etc. But browsers did not implement it that way.
Since you can’t use display:none on your definitions SVG, be sure to use absolute positioning, so that the SVG doesn’t mess up your layout, width & height of 0, and use an aria-hidden=”true” attribute so that it doesn’t confuse screen readers. Even then, as Sarah Drasner discovered, you can have problems with mobile Safari leaving space for the SVG until it has parsed the stylesheet and knows to hide it.
One other thing with gradients: be aware that `userSpaceOnUse` gradients & patterns don’t work correctly in Chrome and IE if the gradient is defined in a different from where it is used. They scale according to the dimension’s of the gradient’s parent SVG, and if that SVG is 0*0, then you don’t get a gradient. (This is also a bug. User space on use was apparently not clear enough!)
Thank you for this comment. The codepen demo you set up has helped my tremendously. I ran into a similar issue in Safari when using absolute positioning to hide the sprite where it was causing half the page to not render / be painted correctly. Setting the sprite to display: block; height: 0; width: 0; was the only way I found to hide the SVG without causing odd rendering issues across various browsers.
By the way, that fill: inherit…. man that is clutch. I never knew about that trick. I always assumed that you absolutely could not override any of the elements defined colors via CSS when using
How about animate svgs from use symbol? 🙁
I’m currently looking into doing something like this with Snap SVG. Worth a try if you have more free-time to work on it than I do.
Assalamu’alaikum Sara.
Nice article
I’m not really good in coding but this one really give me an idea to improve my css skills 🙂
Great breakdown, introduced a few concepts and techniques here that I’ll be sure to use!
Although these are great techniques, I just wish we could style the shadow dom svg elements in the same way we can a normal dom element. That’s the ultimate dream, is this likely to ever be possible?
I want to try this. Thanks for sharing!
Was trying to style my use tags two weeks ago and couldn’t find anything online. Then this awesome post came just at the right time! Thanks Sara! Eid mubarak!
Random thought: Would it be possible to use :nth-child to style the paths? Tried this on a few codepen but seems to only to style the paths in the symbols. Also, looking at the shadom DOM, the svg elements will all have the same IDs. However, I’m guessing this isn’t an issue as these IDs can be accessed by CSS or JavaScript anyway. Correct me if im wrong.
Thanks again!
Nice article! How would one go about styling the shadow DOM via Javascript? You mentioned in this article that this is possible.
There were a lot of things I didn’t know yet that this article taught me. Thanks much!
Great article!
But what about using :hover on an externally linked SVG?
So I wish to change the fill of .box on hover – for instance.
The “sprite” looks like this:
In the example above I simply open the .box-SVG in a browser to test.
Would be great if there were a solution for this, for currently we need to inline the SVG to be able to style it.
Joacim Boive, just save the sprite in localStorage as seen in this article by Osvaldas Valutis and append it inline to the body.
Best of two worlds = caching + css control of svg icon parts
http://osvaldas.info/caching-svg-sprite-in-localstorage
Thank you Sara.Very meticulous and well thought out. It’s kinda blowing my mind.
thanks
very good 🙂
Sara, it is the great article! You truly deserve the award you recently won for all contributions of yours.
But I want to disagree with you about styling
use
element. As you know IE does support inline SVG but(!) doesn’t external ones that usually are icons assets. What does it mean? It means we need to polyfill this experience (I prefer using svg4everybode). It switchuse
by a symbol’s innards so we’re not able to style this element for IE because it no longer exists.Therefore, we need to style
svg
by adding various classes instead of stylinguse
directly.Sara,
Love the article! One question, I noticed that the multiple colors with CSS variables trick only seems to work when the SVG is inlined in the same HTML document. When trying to load it from an SVG sprite (even on the same domain) the variables didn’t work. Anybody know if that is that expected or perhaps a browser bug?
This is awesome! I was very surprised to find such solid work in territory that I expected to be relatively unexplored.
Great article, however it seem like a lot of the tricks don’t work in Chrome when using an external SVG ()
Yeah, it’s like the Cascading stops on the “use” for external ressources, too bad :/
We can still use up to two custom colors by setting our fills to inherit and currentColor, and then styling with css color and fill on the use element.
I’m using gulp to make a sprite then using use/symbol to place them in the page which works great! One issue is that every time I want to add a new icon to the sprite it overwrites all the changes I’ve made, like removing the doctype and certain fills… How do you usually deal with that? Is there a trick? 😀
fix the source image, not the generated sprite, obviously 🙂
Hi Sara! Amazing SVG’s stuff!
Well, I did some experiments and I’d like to know your opinion about this: codepen.io/lagden/pen/gPNJWb
It’s a SVG animation using style sheet
The CodePen above works very well on Firefox and Safari! In Chrome works only the property “fill”!
Should I open an issue at https://bugs.chromium.org about that?
hey sarah, i have problem with animating a circle inside of the svg, can you help me please.
i wondering how can i move my circle to the center of the svg horizontally?
Hi Sarah, my presentation attributes have priority over CSS. If I have this code in my svgsprite.svg file:
<path fill="#F7F7F7" d="M405.6,2….
The presentational attribute "#F7F7F7" doesn't get overridden by my css
.img-class use {
fill: blue;
}
.img-class use path {
fill: inherit;
}
The only way to make this work is to save the svg's in all black out of Illustrator, so they have no presentational attributes, as those are getting priority over my CSS. This works, except for in IE. I can't get your original solution to work, however, as presentational attributes are getting priority.
Thanks so much for taking the time to put this article together – it’s super helpful!
One of the most informative and thorough articles I’ve come across in a while. Thanks for detailing the whys and the hows, and for laying out the future/next steps! No doubt I’ll be revisiting this article soon…
Thanks for the post. How would you address situations where you have multiple nested tags that need to inherit a fill?
Can you use css variables for Filters?
–filter: url(#dropShadow);
Or any other way of applying a filter to a USE element?
Thanks for great post!
I tried (on last technique with CSS Variables) to use transition on hover, but it doesn’t work. Do you have any suggestion?
Thank you for this very detailed article. It really inspired me.
I used some of the tricks in my SVG sprite inliner demo: https://github.com/adriengibrat/svgSprite
Great article! Thanks for sharing!
But how to do if I want to keep the presentation attributes in the markup when I set css on . It seems like the icon become black when I set the below css
svg path {
fill: inherit;
}
The icon inherit the browers’s default fill color.
This article saved a me a bunch of headaches! I though I was going to have to revert away from my use of symbols 🙂
Great article! Really helped me to solve an SVG problem at work, problme that bugged me a few hours.
Thanks!
Great article, thant you!
Thank you for the great article! You saved me from hours debugging the SVG + HTML code!
This article is truly great as a reference article, but thought I would add a little tip to simplify the markup. As I found out there is absolutely no point in embedding the namespace declarations of xlink, when writing the xlink:href declaration. Namespace declarations are not supported in HTML5, and HTML 5 included the HTML, SVG, MathML and XLink namespaces, making the xmlns:xref bit uneeded.
Great artictle!
To keep it up to date I think it’s worth to mention that xlink is going to be deprecated in current web standards, according to this:
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
So from now, you can use just href instead of xlink:href.
Also I have another question.
Is it possible to add SVG icon as background-image(?) or content(?) in CSS for pseudo elements like :after or :before?
I meant using use href=”#icon.svg>.
I want to use inlined (in HTML( SVG symbols in CSS.
Perfect!! Just what I needed and well explained.
I have completed a demo of an external svg with gradients that works in chrome, safari and firefox. Plunker is here: http://plnkr.co/HvRiDR. The short answer is you have to inline the gradients into the parent html file, but the rest of the svg code remains external. Styling is done with css variables.
I am looking now to make the content of dynamic. For example lets say I wanted to add custom text that is unique for each invocation. It appears, that javascript can’t reach inside of because of the ShadowDom. Is that really true?
Later I want to animate parts of the svg, lets say change the embedded text repeatedly.
Anyone have experience with the tag and JS manipulation?
Sara, this is brilliant. I just learned a lot of new tricks! Thanks so much.
Within my `use` element, it shows `#shadow-root (closed)` which I assume means I’m not able to have access.
Try to specify full address to your SVG-file in “xlink:href” attribute of tag.
nice, good job, keep it up pls my friend, greetings and thanks for all,
Sorry but the fill:inherit does not work on Firefox Dev Edition and not in Chrome with an external sprite. Is that article outdated?
Any way to have a pattern for fill?
Lovely work. Well done 🙂
Thank you for sharing this! Such a quick fix.
Know this is an older article, but has anyone else noticed the basic color override example doesn’t work in Firefox.
https://codepen.io/SaraSoueidan/pen/f15cec4a61e753259bd768de2e20500b
Simply dropping the inline, “presentation attributes,” fill makes it work. It was messing up one of my sites recently. Luckily playing with Sara’s Codepen helped.
Looks like it was probably just an issue with overriding the proper elements. Haven’t sorted out the main specificity issue, but another workaround that got things functioning was to do a global override with `*`.