
Hagamos iconos multicolores con símbolos SVG y variables CSS
Atrás quedaron los días de usar imágenes y sprites CSS para crear íconos para la web. Con la explosión de las fuentes web, las fuentes de iconos se han convertido en la solución número uno para mostrar iconos en sus proyectos web.
Las fuentes son vectores, por lo que no tiene que preocuparse por la resolución. Se benefician de las mismas propiedades CSS que el texto. Como resultado, tiene control total sobre el tamaño, el color y el estilo. Puede agregar transformaciones, efectos y decoraciones como rotaciones, subrayados o sombras.

Sin embargo , las fuentes de iconos no son perfectas , por lo que un número creciente de personas prefiere usar imágenes SVG en línea. CSS Tricks escribió una lista de áreas donde las fuentes de iconos se quedan cortas en comparación con los elementos SVG nativos: nitidez, posicionamiento o incluso fallas debido a la carga entre dominios, errores específicos del navegador y bloqueadores de anuncios. Ahora puede eludir la mayoría de estos problemas, por lo que las fuentes de iconos son una opción segura.
Sin embargo, hay una cosa que sigue siendo absolutamente imposible con las fuentes de iconos: el soporte multicolor . Solo SVG puede hacer esto.
TL; DR : esta publicación profundiza en el cómo y el por qué. Si quieres comprender todo el proceso de pensamiento, sigue leyendo. De lo contrario, puede consultar el código final en CodePen.
Configuración de iconos de símbolos SVG
El problema con los SVG en línea es lo detallados que son. No desea copiar / pegar todas esas coordenadas cada vez que necesite usar el mismo icono. Esto sería repetitivo, difícil de leer y un dolor de mantener.
Con los iconos de símbolos SVG, tiene una copia de cada elemento SVG y puede crear una instancia de ellos en cualquier lugar con una referencia.
Empiece por incluir el SVG en línea, ocultarlo, envolverlo en un th
an id attribute.
my-first-icon
The full SVG markup is included once and hidden in the HTML.
Then, all you have to do is instantiate the icon with a
se> element.
This will display an exact copy of your original SVG icon.

Original text

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 xlink:href
.
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 that they won’t inherit parent text color like icon fonts do, but you can still style them in CSS.
// HTML
// CSS.icon { width: 100px; height: 100px; fill: red;}
From here, you can create other instances of the same icon with a different fill color.
// HTML
// CSS.icon { width: 100px; height: 100px;}.icon-red { fill: red;}.icon-blue { fill: blue;}
It works, but this isn’t exactly what we want. So far, all we have done can be achieved with a regular icon font. What we want is to 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.
// HTML my-first-icon
// CSS.icon-colors .path1 { fill: red;}.icon-colors .path2 { fill: green;}.icon-colors .path3 { fill: blue;}
This won’t work.
We’re trying to style .path1
, .path2
and .path3
as if they were nested in .icon-colors
, but technically speaking they’re not. The se> 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 it’s 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, and 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.
.parent { --custom-property: red; color: var(--custom-property);}
All children of .parent
will have red text.
.parent { --custom-property: red;}.child { color: var(--custom-property);}
All .child
nested in .parent
elements will have red text.
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.
// HTML my-first-icon
// CSS.icon-colors { --color-1: #c13127; --color-2: #ef5b49; --color-3: #cacaea;}
And… it works! ?

From now on, all we need to do to create an instance with a different color scheme is to create a new class.
// HTML
// CSS.icon-colors-alt { --color-1: brown; --color-2: yellow; --color-3: pink;}
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 fill
declaration.
.icon-monochrome { fill: grey;}
Your fill
declaration will work because the fill
attributes on the original SVG are set with undefined CSS variables values.
What to name my CSS variables?
There are usually 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 --cup-handle-color
.
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 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.
:root
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.
:root { --color-1: red; --color-2: green; --color-3: blue; --color-4: var(--color-1);}
.icon-colors-alt { --color-1: brown; --color-2: yellow; --color-3: pink; --color-4: orange;}
However, there are major drawbacks to 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-1
, --fill-color-2
, or use namespaces like --star-fill-color
, --cup-fill-color
.
var() default
The var()
function, which you use to assign a CSS variable to a property, can take a default value as a second argument.
my-first-icon
Until you define --color-1
, --color-2
and --color-3
, the icon will use the default values you set for each th>. 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 singl
e 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, just because a feature isn’t supported by a browser you need to cater to, that doesn’t mean 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 @mixin
.
@mixin icon-colors($fallback: black) { fill: $fallback; @content;}
We can now define color schemes without worrying about browser compatibility.
.cup { @include icon-colors() { --cup-color: red; --smoke-color: grey; };}
.cup-alt { @include icon-colors(green) { --cup-color: green; --smoke-color: grey; };}
Passing the CSS variables in the mixin through @content
is optional. If you do it outside, the compiled CSS will be the same. But it can be helpful to package it all in one place: you can fold snippets in your editor and visually identify declarations that go together.
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!
Originally published at frontstuff.io.