• Let’s make multi-colored ICONS with SVG symbols and CSS variables
  • By Sarah Dayan
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: PTHFLY
  • Proofreader: cherry, Raoul1996

Use SVG symbols and CSS variables to implement colorful ICONS

Gone are the days of making Web ICONS using images and CSS sprites. 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’re just like text because they have CSS properties, which means you can apply size, color, and style. You can add transformations, effects, and decorations, such as rotations, underlining, or shadows.

No wonder projects like Font Awesome have been downloaded more than 15 million times on NPM alone to date.

But icon fonts aren’t perfect, which is why more and more people are using inline SVG. CSS Tricks writes about how icon fonts are inferior to native SVG elements: sharpness, positioning, or failure due to cross-domain loading, browser-specific errors, and AD blockers. Now that you can avoid most of these problems, using icon fonts is generally a safe choice.

However, there is one thing that is absolutely impossible for icon fonts: multicolor support. Only SVG can do that.

Abstract: This post goes into the how and why. Reading is recommended if you want to understand the whole thought process. Otherwise, you can view the final code directly in CodePen.

Sets the SVG logo icon

The problem with inline SVG is that it can be very verbose. You don’t want to have to copy/paste all the coordinates every time you use the same icon. This will be very repetitive, difficult to read, and harder to maintain.

With the SVG symbol icon, you just need to have an SVG element and reference it wherever you need it.

After adding inline SVG and hiding it, wrap it with

and identify it with 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>
Copy the code

The entire SVG markup is wrapped once and is hidden in HTML.

Then, all you have to do is instantiate the icon with a

tag.

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

This will display a copy of the original SVG icon.

** that’s it! ** looks great, right?

You may have noticed the interesting xlink:href attribute: this is the link between your instance and the original SVG.

It should be mentioned that xlink:href is a deprecated SVG attribute. Although most browsers still support it, you should use the **href** instead. The problem is that some browsers like Safari don’t support SVG resource references using href, so you still need to provide the xlink:href option.

Just to be safe, use both.

Add some color

Unlike fonts, color has no effect on SVG ICONS: you must use the fill attribute to define a color. This means that they will not inherit the parent text color as icon fonts do, but you can still define their style in CSS.

// HTML
<svg class="icon">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon {
  width: 100px;
  height: 100px;
  fill: red;
}
Copy the code

Here, you can create different instances of the same icon with different fill colors.

// HTML
<svg class="icon icon-red">
  <use xlink:href="#my-first-icon" />
</svg>
<svg class="icon icon-blue">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon {
  width: 100px;
  height: 100px;
}
.icon-red {
  fill: red;
}
.icon-blue {
  fill: blue;
}
Copy the code

So that works, but it’s not exactly what we expected. Everything we’ve done so far can be done using a plain icon font. What we want is to have different colors in the position of the icon. We want to fill each path with a different color without changing other instances, and we want to be able to override it if necessary.

First, you might be tempted to rely on features.

// HTML
<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>
// CSS
.icon-colors .path1 {
  fill: red;
}
.icon-colors .path2 {
  fill: green;
}
.icon-colors .path3 {
  fill: blue;
}
Copy the code

It doesn’t work.

We tried to set the styles of.path1,.path2, and.path3 as if they were nested in.icon-colors, but strictly speaking, they weren’t. The

tag is not a placeholder that will be replaced by your SVG definition. This is a reference to copy what it points to as shadow DOM 😱.

** So what do we do next? ** How can we affect children in a cultural way when they are not in the DOM?

CSS variables save the world

In CSS, attributes are inherited from parent elements to child elements. If you assign a text color to the body, all text on the page will inherit that color until overwritten. The parent element is not aware of the child element, but inheritable styles continue to propagate.

In our previous example, we inherited the fill property. Looking back, you’ll see that the class we declared to be filled with color is attached to the instance, not the definition. This is why we can give different colors to different entities of the same definition.

Here’s the problem: we want to pass different colors to different paths of the original SVG, but can only inherit from a single fill attribute.

This is where CSS variables come in.

Just like any other property, CSS variables are declared in a rule set. You can assign any valid CSS value with any name you want. Then, you declare it as a value, either for itself or for some other child property, and that will be inherited.

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

All children of.parent have red text.

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

All.children nested within the.parent tag have red text.

Now, let’s apply this concept to SVG notation. We will use the fill attribute for each part of the SVG definition and set it to a different CSS variable. Then, we’ll assign them different colors.

// HTML
<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>
// CSS
.icon-colors {
  --color-1: #c13127;
  --color-2: #ef5b49;
  --color-3: #cacaea;
}
Copy the code

And then… Effective 🎉!

Now, to create instances with different color schemes, all we need to do is create a new class.

// HTML
<svg class="icon icon-colors-alt">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon-colors-alt {
  --color-1: brown;
  --color-2: yellow;
  --color-3: pink;
}
Copy the code

If you still want to have monochrome ICONS, you don’t have to repeat the same color in every CSS variable. Instead, you can declare a single fill rule: if the CSS variable is not defined, it will return to your fill declaration.

.icon-monochrome {
  fill: grey;
}
Copy the code

yourfillThe declaration will take effect because of the original SVGfillProperties are defined by unset CSS variable values.

How do I name my CSS variables?

When it comes to naming CSS, there are usually two approaches: descriptive or semantic. Description means to tell a color what it is: if you store #ff0000 you can call it –red. If you use #ff0000 to give a color to a coffee cup handle, you can call it cup-handle-color.

Descriptive naming may be your instinct. Seems cleaner, because #ff0000 can be used for more than just the handle of a coffee cup. A –red CSS variable can be reused for other icon paths that need to be red. After all, that’s how pragmatism works in CSS. And it’s a good system.

The problem is, in our case, we can’t apply the odd class to the tag we want to style. The principle of pragmatism does not apply because we have a separate reference for each icon, and we have to style it through class changes.

Using semantic class names, such as –cup-handle-color, is more useful in this case. When you want to change the color of part of an icon, you immediately know what it is and what you need to rewrite. No matter what color you assign, the class name will always be associated.

Default or not default, that’s the question

It’s tempting to make the multicolor version of your icon the default. That way, you don’t need to set up extra styles, just add your own classes if necessary.

There are two ways to do this: :root and var() default.

:root

In the :root selector you can define all your CSS variables. This will unify them in one place, allowing you to “share” similar colors. Root has the lowest priority and can be easily overridden.

: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;
}
Copy the code

However, this approach has one major drawback. First, it can be confusing to separate color definitions from their respective ICONS. 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 associate your CSS variables, so you can’t reuse the same name.

Most of the time, when an icon uses only one color, I use –fill-color. Simple, easy to understand, and makes sense for all ICONS that only need one color. If I had to declare all variables in the :root declaration, I wouldn’t have a few — fill-colors. I would be forced to define –fill-color-1, –fill-color-2 or use a namespace like –star-fill-color, –cup-fill-color.

Var () by default

You can use the var() function to assign a CSS variable to a property, and its second argument can be set to some default value.

<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>
Copy the code

ICONS will use the default values you set for each until you have defined –color-1, –color-2, and –color-3. This solves the global association problem when we use :root, but be careful: you now have a default value, and it will work. As a result, you can no longer use a single fill declaration to define monochrome ICONS. You will have to assign color to each CSS variable used for the icon one by one.

Setting defaults can be useful, but it’s a compromise. I suggest you don’t make a habit of doing it and only do it when it’s helpful for a given project.

How browser-friendly is all that?

CSS variables are compatible with most modern browsers, but as you might expect, Internet Explorer is completely incompatible. Because Microsoft pulled the plug on IE11 development to support Edge, IE never had a chance to catch up.

Now, just because a feature isn’t compatible with a particular browser (and you have to adapt) doesn’t mean you have to give it up altogether. In this case, consider elegant downgrading: colorful ICONS for modern browsers and back-up fill colors for backward ones.

What you want to do is set a declaration that fires only when CSS variables are not supported. This can be done by setting the fill property for the backup color: if the CSS variable is not supported, it is not even considered. If they are not supported, your fill declaration will take effect.

If you use Sass, this can be abstracted as an @mixin.

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

Now you can define any color scheme you want regardless of browser compatibility.

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

Pass in mixin@contentPassing CSS variables is also optional. If you do this outside, the compiled CSS will look the same. But it helps to pack together: you can fold fragments in your editor and then use your eyes to distinguish statements together.

View the pen in a different browser. In the latest versions of Firefox, Chrome, and Safari, the last two cups have red body gray smoke and blue body gray smoke respectively. In IE and Edge with version less than 15, the body and smoke of the third cup are all red, and the fourth cup is all blue! ✨

If you want to learn more about SVG symbol ICONS (or SVG in general), I highly recommend reading everything Sara Soueidan has written. If you have any questions about CSS symbol ICONS, don’t hesitate to contact me on Twitter.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.