Long gone are the days of using images and CSS sprites to make icons for the web. With the explosion of web fonts, icon fonts have become the number one solution for displaying icons in your web projects.
Fonts are vectors, so you don’t have to worry about resolution. They benefit from the same CSS properties as text. As a result, you have full control over size, color, and style. You can add transforms, effects, and decorations such as rotations, underlines or shadows.
Icon fonts aren’t perfect though, which is why a growing number of people prefer using inline SVG images. CSS Tricks wrote a list of areas where icon fonts fall short compared to native SVG elements: sharpness, positioning, or even failures because of cross-domain loading, browser-specific bugs, and ad-blockers. Now you can circumvent most of these issues, and usually, icon fonts are a safe choice.
Yet, there’s one thing that remains absolutely impossible with icon fonts: multicolor support. Only SVG can do this.
TL;DR: this post goes in-depth in the how and why. If you want to understand the whole thought process, read on. Otherwise you can look at the final code on CodePen.
Setting up SVG symbol icons
The problem with inline SVG is how verbose they are. You don’t want to copy/paste all that amount of coordinates every single time you need to use the same icon. This would be repetitive, hard to read and a pain to maintain.
With SVG symbol icons, you have one copy of each SVG element, and you instantiate them anywhere with a reference.
You start by including the SVG inline, hide it, wrap it in a
<symbol> and identify it with an
Then, all you have to do is instantiate the icon with a
That’s it! Pretty nice, right?
You probably noticed the funny
xlink:href attribute: this is the link between your instance and the original SVG.
It’s important to mention that
xlink:href is a deprecated SVG attribute. Even if most browsers still support it, you should use
href instead. Now the thing is, some browsers like Safari don’t support SVG resource references through the
href attribute, so you still need to provide
To be safe, provide both attributes.
Adding some color
Unlike with fonts,
color doesn’t have any effect on SVG icons: you must use the
fill attributes to define a color. This means they won’t inherit parent text color like icon fonts do, but you can still style them in CSS.
From here, you can create other instances of the same icon with a different fill color.
It works, but this isn’t exactly what we want. So far, all we did can be achieved with a regular icon font. What we want is have a different color for each part of the icon. We want to fill each path with a different color, without altering other instances, and we want to be able to override it if necessary.
At first, you might be tempted to rely on specificity.
This won’t work.
We’re trying to style
.path3 as if they were nested in
.icon-colors, but technically speaking they’re not. The
<use> element isn’t a placeholder that gets replaced by your SVG definition. It’s a reference which clones the content it’s pointing to into the shadow DOM 😱
What can we do then? How can we affect children content in a scoped way when said children aren’t in the DOM?
CSS variables to the rescue
In CSS, some properties are inherited from ancestors to children. If you assign a text color to the
body, all the text in the page will inherit that color until they’re overridden. The ancestor isn’t aware of the children, but the inheritable styles are still propagated.
In our early example, we inherited the
fill property. Look again, you’ll see that the class in which we declared a
fill color is appended on the instances, not the definitions. This is how we were able to get different colors for each instance of a single definition.
Now here’s the problem: we want to pass different colors to different paths of the original SVG, but there’s only one
fill attribute we can inherit from.
Meet CSS variables.
CSS variables are declared within rulesets just like any other property. You can name them anything you want, and assign them any valid CSS value. Then, you declare it as a value for itself, or any child property, and it will be inherited.
Now let’s apply this concept to our SVG symbol. We’ll use the
fill attribute on each path of the SVG definition, and set them to different CSS variables. Then, we’ll assign them different colors.
And… it works! 🎉
From now on, all we need to create an instance with a different color scheme is to create a new class.
If you still want to have monochrome icons, you don’t have to repeat the same color on every CSS variable. Instead, you can declare a single
fill rule: because CSS variables aren’t defined, it will fall back on your
What to name my CSS variables?
There usually are two routes you can take when it comes to naming things in CSS: descriptive or semantic. Descriptive means calling a color what it is: if you’re storing
#ff0000, you’d call it
--red. Semantic means calling the color by how it’s applied: if you’re using
#ff0000 for the handle of a coffee cup, you’d call it
Descriptive names might be your first instinct. It feels DRYer since
#ff0000 can be used for other things than the handle of the coffee cup. A
--red CSS variable is reusable for other icon paths that need to be red. After all this is how utility-first CSS works and it’s a fine system.
Problem is, in our case we can’t apply granular classes to the elements we want to style. Utility-first principles can’t apply because we have a single reference for each icon, and we have to style it through class variations.
Using semantic class names, like
--cup-handle-color for example, makes more sense for this use case. When you want to change the color of a part of an icon, you instantly know what it is and what to override. The class name will remain relevant, no matter what color you assign.
To default or not to default
It’s tempting to make the multi-colored version of your icons be their default state. This way, you could use them with no need for extra styling, and you would add your own classes only when necessary.
There are two ways to achieve that: :root and var() default.
You can define all your CSS variables on the
:root selector. This keeps them all in one place and allows you to “share” similar colors.
:root has the lowest priority, so it remains easy to override.
However, there are major drawbacks with this method. First, keeping color definitions separate from their respective icons can be confusing. When you decide to override them, you have to go back and forth between the class and the
:root selector. But more importantly, it doesn’t allow you to scope your CSS variables, thus keeps you from reusing the same names.
Most of the time, when an icon only uses one color, I use the
--fill-color name. It’s simple, understandable, and it makes sense to use the same name for all icons that only need one fill color. If I have to declare all variables in the
:root declaration, I can’t have several
--fill-color. I’ll be forced to define
--fill-color-2, or use namespaces like
var() function, which you use to assign a CSS variable to a property, can take a default value as a second argument.
Until you define
--color-3, the icon will use the default values you set for each
<path>. This solves the global scope issue we have when using
:root, but be careful: you now have a default value and it’s doing its job. As a result, you can’t use a single
fill declaration to define monochrome icons anymore. You’ll have to assign the color to every CSS variable used on the icon, one by one.
Setting default values can be useful, but it’s a tradeoff. I suggest you don’t make it a habit, and only do it when it makes sense for a given project.
How browser-friendly is all that?
CSS variables are compatible with most modern browsers but as you probably expect it, Internet Explorer doesn’t support it at all. Not even IE11, and since development was discontinued in favor of Edge, there’s no chance it will ever get up to speed.
Now, not because a feature isn’t supported by a browser you need to cater to means you have to rule it out altogether. In such cases, go for graceful degradation: offer multi-colored icons to modern browsers, and provide a fallback fill color for older ones.
What you want to do is set a declaration that will only work if CSS variables aren’t supported. This can be achieved by setting the
fill property to the fallback color: if CSS variables are supported, it won’t even be taken into account. If they’re not, your
fill declaration will apply.
If you’re using Sass, this can be abstracted into a
We can now define color schemes without worrying about browser compatibility.
Check out this pen on different browsers. On up-to-date versions of Firefox, Chrome, and Safari, the last two cups will respectively be red with grey smoke and blue with grey smoke. On Internet Explorer and Edge before version 15, the third cup will be all red and the fourth will be all blue! ✨
If you want to learn more about SVG symbol icons (and SVG in general), I strongly suggest you read everything by Sara Soueidan. And if you have any question about CSS symbol icons, don’t hesitate to hit me up on Twitter!