⬆️ ⬇️

Making multicolored icons using SVG symbols and CSS variables

Icons



Long gone are the days when images and CSS sprites were used for icons on the web. With the development of web fonts, the number 1 for icon display on websites has become icon fonts.



Fonts are vectorial, so you don’t need to worry about screen resolution. For them, you can use the same CSS properties as for text. As a result, you have complete control over their size, color and style. You can add effects to them, transform or decorate them. For example, rotate , underline or add text-shadow .

')

Icon fonts are not perfect , so more and more people prefer to use embedded SVG images. CSS Tricks has an article describing moments in which icon fonts are inferior to SVG elements : sharpness, positioning, cross-domain load failures, browser features, and ad blockers. Now you can bypass most of these problems, which, in general, makes the use of icon fonts safe.



Yes, one more thing that is absolutely impossible when using icon fonts: support for multicolor . Only SVG can do this.



TL; DR : This post allows you to understand how and why. If you want to understand the whole process, read on. Otherwise, you can see the final code on CodePen .





Customizing SVG icon symbols



The problem with embedded SVGs is that they are complex. You do not want to copy-paste all these coordinates every time you need to use the same icon. It will result in repetitive, hard-to-read and hard-to-maintain code.



Using SVG symbols allows you to have only one copy of each SVG element and use it anywhere using the link.



Start by adding the embedded SVG, hide it, wrap the contents in the symbol tag and give it an id .



 <svg xmlns="http://www.w3.org/2000/svg" style="display: none"> <symbol id="my-first-icon" viewBox="0 0 20 20"> <title>my-first-icon</title> <path d="..." /> </symbol> </svg> 


The full markup of the SVG element is written once and hidden.



Then all you need to do is create a copy of the icon using the use element.



 <svg> <use xlink:href="#my-first-icon" /> </svg> 


Get an exact copy of your original SVG-icons.



Black cup



Here she is! Pretty sweet, right?



You probably noticed the xlink:href attribute - this is the link between your icon and the original SVG image.



It is important to note that xlink:href is an obsolete SVG attribute. Even if most browsers still support it, you should use href instead. But the fact is that some browsers, such as Safari, do not support links to SVG resources through the href attribute, so you still need to specify xlink:href .



For security, use both attributes.



Adding color



Unlike fonts, the color property does not affect SVG icons: you must use the fill attribute to specify a color. This means that they do not inherit the parent color of the text, but you can still stylize them through CSS.



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


 .icon { width: 100px; height: 100px; fill: red; } 


Consequently, you can create other instances of the same icon in different colors.



 <svg class="icon icon-red"> <use xlink:href="#my-first-icon" /> </svg> <svg class="icon icon-blue"> <use xlink:href="#my-first-icon" /> </svg> 


 .icon { width: 100px; height: 100px; } .icon-red { fill: red; } .icon-blue { fill: blue; } 


It works, but it’s not exactly what we want. Everything that we have done so far can be done with the help of a regular icon font. What we want is to make each part of an icon of a different color . We want to fill each part of one icon with different colors without changing its other instances, and we want it to be possible to redefine these colors if necessary.



First, you might have an idea of ​​relying on specificity.



 <svg xmlns="http://www.w3.org/2000/svg" style="display: none"> <symbol id="my-first-icon" viewBox="0 0 20 20"> <title>my-first-icon</title> <path class="path1" d="..." /> <path class="path2" d="..." /> <path class="path3" d="..." /> </symbol> </svg> <svg class="icon icon-colors"> <use xlink:href="#my-first-icon" /> </svg> 


 .icon-colors .path1 { fill: red; } .icon-colors .path2 { fill: green; } .icon-colors .path3 { fill: blue; } 




It will not work.



We are trying to set styles for .path1 , .path2 and .path3 as if they were embedded in .icon-colors , but technically it is not. use element is not a placeholder, which is replaced by a specific SVG. This is the link that copies the contents of what it points to in the shadow DOM .



And what should we do then ? How can we affect the contents of children when they say that there are no children in the DOM?



CSS variables will help



In CSS, some properties are inherited by children from their ancestors. If you specify the text color for the body , all text on the page will inherit this color until it is overridden. The ancestor does not know the children, but the inherited properties are still transmitted.



In the example above, we inherit the fill property. Look again and you will see that the class in which we defined this color fill added to the icon instances, not to its definition. So we were able to get multi-colored copies of one source.



But here's the problem: we want to convey different colors for different parts of the original SVG icon, but there is only one fill attribute that we can inherit.



Meet the CSS variables .



CSS variables are declared in rule sets just like any other property. You can name them whatever you want and assign them any valid CSS value. Then you define through this variable the value of the property of the element itself or its child, and it will be inherited .



 .parent { --custom-property: red; color: var(--custom-property); } 


All .parent children will have red text.



 .parent { --custom-property: red; } .child { color: var(--custom-property); } 


All .child embedded in .parent will have red text.



Now let's apply this concept to our SVG symbol. We will use the fill attribute for each part of the path in the definition of our SVG icon and set them to different CSS variables. Then we assign them different colors.



 <svg xmlns="http://www.w3.org/2000/svg" style="display: none"> <symbol id="my-first-icon" viewBox="0 0 20 20"> <title>my-first-icon</title> <path fill="var(--color-1)" d="..." /> <path fill="var(--color-2)" d="..." /> <path fill="var(--color-3)" d="..." /> </symbol> </svg> <svg class="icon icon-colors"> <use xlink:href="#my-first-icon" /> </svg> 


 .icon-colors { --color-1: #c13127; --color-2: #ef5b49; --color-3: #cacaea; } 


And ... it works !



Multi-color cup



From this point on, all we need to create a copy with a different color scheme is to write a new class.



 <svg class="icon icon-colors-alt"> <use xlink:href="#my-first-icon" /> </svg> 


 .icon-colors-alt { --color-1: brown; --color-2: yellow; --color-3: pink; } 


If you still want a monochrome icon, you do not need to repeat the same color for each CSS variable . Instead, you can define one rule for fill : in this case, CSS variables are not defined, the definition of the fill property will be used.



 .icon-monochrome { fill: grey; } 


The fill property will work because the fill attribute of the source SVG is set with undefined CSS variable values.



How to name my CSS variables?



Usually use one of two ways of naming in CSS: descriptive or semantic . Descriptive - this means calling the variable by the name of the color itself: if your color is #ff0000 , you call the variable --red . Semantic is the name of the variable according to its purpose: if you use the color #ff0000 for the cup handle, you call the variable - --cup-handle-color .



Perhaps your first wish would be to use descriptive naming. This seems natural since the color #ff0000 can be used for other things besides the cup handle. The CSS variable --red can --red be used for other parts of the icon, which should be red. After all, this is how utility-first CSS works, and it’s good .



The problem is that in our case we cannot apply atomic classes to the elements that we want to stylize . The principles of utility-first are not applicable, since we only have a link for each icon, and we must stylize it through variations of classes.



Using semantic naming --cup-handle-color , such as - --cup-handle-color , is more appropriate in our case. When you need to change the color of some part of the icon, you know exactly what and how you need to override it. The class name will remain relevant no matter what color you have assigned.



Default or non default



A tempting idea is to make your default icons multicolored. In this case, you can use them without additional styling, and only if necessary add a separate class.



This can be achieved in two ways: through : root or via var () default .



: root



You can define all your CSS variables in the selector :root . This will keep them all in one place and “share” similar colors. :root has the lowest specificity, so it is easy to override it.



 :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, this method has significant drawbacks . For starters, keeping color definitions apart from the corresponding icons can be confusing. When you decide to redefine them, you have to jump back and forth between the class selector and :root . But more importantly, this method does not allow you to change your CSS variables , so you cannot reuse the same names.



In most cases, when only one color is used in the icon, I call the variable --fill-color . It's simple, understandable and allows you to use this name for all monochrome icons. If I define all the variables in the selector :root , I will not be able to have several --fill-color variables. I will be forced to define --fill-color-1 , --fill-color-2 or use namespaces such as --star-fill-color , --cup-fill-color .



var () default



The var() function, which you use to assign a CSS variable to a property, can take the default value as the second argument.



 <svg xmlns="http://www.w3.org/2000/svg" style="display: none"> <symbol id="my-first-icon" viewBox="0 0 20 20"> <title>my-first-icon</title> <path fill="var(--color-1, red)" d="..." /> <path fill="var(--color-2, blue)" d="..." /> <path fill="var(--color-3, green)" d="..." /> </symbol> </svg> 


Unless you specify --color-1 , --color-2 and --color-3 , the icon will use the default values ​​set for each path . This solves the global definition problem that we have when using :root , but be careful: you now have a default value and it does its job . Thus, you can no longer write only one fill property to make the icon monochrome. You need to assign a color to each CSS variable used in the icon, separately.



Setting defaults may be useful, but it's a trade off. I suggest not getting used to it, and doing it only when it makes sense for a specific project.



How is all this supported by browsers?



CSS variables are supported by all modern browsers , but, as you probably guessed, IE does not support them, at all . Even IE11, and since its development has been discontinued in favor of the Edge, there is no chance that it will ever support them.



But just because the variables do not work in the browser, which you need to maintain, does not mean that you have to completely abandon them. In such cases, use graceful degradation : offer multicolored icons to modern browsers, and for old ones specify a spare color.



All you have to do is add a color definition, which will only work if CSS variables are not supported. This can be achieved by specifying the fill property with a backup color. If CSS variables are supported, this declaration will not be taken into account. If not, it will work.



If you use Sass, you can write this check to @mixin .



 @mixin icon-colors($fallback: black) { fill: $fallback; @content; } 


Now you can define color schemes without worrying about browser support.



 .cup { @include icon-colors() { --cup-color: red; --smoke-color: grey; }; } .cup-alt { @include icon-colors(green) { --cup-color: green; --smoke-color: grey; }; } 


Passing CSS variables to mixin via @content optional. If you do this outside, the compiled CSS will be the same. But it may be helpful to keep it all in one place.



You can check this example in different browsers. In the latest versions of Firefox, Chrome and Safari, the last two cups will be respectively red with gray vapor and blue with gray vapor. In Internet Explorer and Edge below version 15, the third icon will be all red, and the fourth - all blue.

Source: https://habr.com/ru/post/348194/



All Articles