Using CSS to Create SVG Icons

Updated Feb 7, 2018

Usage of SVG for web icons continues to grow given more widespread browser support and the advantages over other approaches: font libraries require an additional external resource full of icons you’ll never use while raster images don’t scale to different resolutions and have larger file sizes than SVG. There are several approaches to implementing an SVG icon system, each with its own benefits and drawbacks.

This post demonstrates a pure CSS/Sass approach that works in IE10 and greater and other major browsers and allows for reusable SVG icons that can be styled for color and size:

Getting started

For the sake of brevity the examples use a simple circle element but the approach works with any sort of SVG content; most icons will likely be path elements. See the end of this post for code examples to create the icons displayed above.

The goal is to end up with compiled CSS that looks something like the following:

Final CSS
.icon-circle {
  background-image:url('data:image/svg+xml;charset=utf-8,%3Csvg viewBox=%220 0 10 10%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Ccircle fill=%22%23fd9827%22 cx=%225%22 cy=%225%22 r=%225%22 /%3E%3C/svg%3E');
  height:32px;
  width:32px;
}

Note that the , #, <, and > characters are encoded as required by Firefox and IE (don’t worry, we do the encoding with functions). Applying the icon-circle class to an HTML element (e.g., <div class=”icon-circle”></div>) yields a 32×32 orange circle:

This tutorial focuses on how to generate the background-image property value using Sass functions. The raw Sass file that outputs the above CSS will be as follows:

Sass
.icon-circle {
  background-image:get-icon-circle(#fd9827);
  height:32px;
  width:32px;
}

Defining utility functions

From the above we know that we need to define a function get-icon-circle that takes a fill color as its single argument and returns a background-image property value. Typically I’ll create a file _icons.scss that includes all icon-related functions though the following can obviously be included anywhere in your project to suit your needs:

_icons.scss Copy
// Characters to encode.
$encodings: (
  ('<', '%3C'),
  ('>', '%3E'),
  ('#', '%23'),
  ('"', '%22')
);
// Replaces $search in $string with $replace.
@function str-replace($string, $search, $replace: '') {
  $index: str-index($string, $search);
  @if $index {
    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
  }
  @return $string;
}
// Returns background-image property value for icon.
@function get-icon($content, $viewBox) {
  $svg: '<svg viewBox="' + $viewBox + '" xmlns="http://www.w3.org/2000/svg">' + $content + '</svg>';
  @each $char, $encoded in $encodings {
    $svg: str-replace($svg, $char, $encoded);
  }
  @return url('data:image/svg+xml;charset=utf-8,' + $svg);
}

Defining icon functions

With the utility functions defined we can now define the get-icon-circle function that we used above:

get-icon-circle Copy
// Function to generate the circle icon.
@function get-icon-circle ($fill) {
  $content: '<circle fill="#{$fill}" cx="10" cy="10" r="5" />';
  @return get-icon($content, '0 0 10 10');
}

The get-icon-circle function takes a $fill color that’s inserted into the icon’s SVG definition and returns a URL for use as a background-image property value. From here we can follow the same template to define functions to generate other icons. By way of example we’ll define the functions used to generate the @, cog, and anchor icons shown at the beginning of the post:

Defining other icons Copy
@function get-icon-at ($fill) {
  $content: '<path fill="#{$fill}" d="M1100 775q0-108-53.5-169t-147.5-61q-63 0-124 30.5t-110 84.5-79.5 137-30.5 180q0 112 53.5 173t150.5 61q96 0 176-66.5t122.5-166 42.5-203.5zm564 121q0 111-37 197t-98.5 135-131.5 74.5-145 27.5q-6 0-15.5.5t-16.5.5q-95 0-142-53-28-33-33-83-52 66-131.5 110t-173.5 44q-161 0-249.5-95.5t-88.5-269.5q0-157 66-290t179-210.5 246-77.5q87 0 155 35.5t106 99.5l2-19 11-56q1-6 5.5-12t9.5-6h118q5 0 13 11 5 5 3 16l-120 614q-5 24-5 48 0 39 12.5 52t44.5 13q28-1 57-5.5t73-24 77-50 57-89.5 24-137q0-292-174-466t-466-174q-130 0-248.5 51t-204 136.5-136.5 204-51 248.5 51 248.5 136.5 204 204 136.5 248.5 51q228 0 405-144 11-9 24-8t21 12l41 49q8 12 7 24-2 13-12 22-102 83-227.5 128t-258.5 45q-156 0-298-61t-245-164-164-245-61-298 61-298 164-245 245-164 298-61q344 0 556 212t212 556z"/>';
  @return get-icon($content, '0 0 1792 1792');
}
@function get-icon-cog ($fill) {
  $content: '<path fill="#{$fill}" fill-rule="evenodd" d="M0,375 L198,375 L58,235 L235,58 L375,198 L375,0 L625,0 L625,198 L765,58 L942,235 L802,375 L1000,375 L1000,625 L802,625 L942,765 L765,942 L625,802 L625,1000 L375,1000 L375,802 L235,942 L58,765 L198,625 L0,625z M325,500 A150,150 0 0,1 675,500 A150,150 0 0,1 325,500z" />';
  @return get-icon($content, '0 0 1000 1000');
}
@function get-icon-anchor ($fill) {
  $content: '<path fill="#{$fill}" d="M960 256q0-26-19-45t-45-19-45 19-19 45 19 45 45 19 45-19 19-45zm832 928v352q0 22-20 30-8 2-12 2-12 0-23-9l-93-93q-119 143-318.5 226.5t-429.5 83.5-429.5-83.5-318.5-226.5l-93 93q-9 9-23 9-4 0-12-2-20-8-20-30v-352q0-14 9-23t23-9h352q22 0 30 20 8 19-7 35l-100 100q67 91 189.5 153.5t271.5 82.5v-647h-192q-26 0-45-19t-19-45v-128q0-26 19-45t45-19h192v-163q-58-34-93-92.5t-35-128.5q0-106 75-181t181-75 181 75 75 181q0 70-35 128.5t-93 92.5v163h192q26 0 45 19t19 45v128q0 26-19 45t-45 19h-192v647q149-20 271.5-82.5t189.5-153.5l-100-100q-15-16-7-35 8-20 30-20h352q14 0 23 9t9 23z"/>';
  @return get-icon($content, '0 0 1792 1792');
}

Using the icon functions

Again assuming that we’ve defined our icon functions in a file _icons.scss we can use them in other .scss files as follows:

Using the icon functions Copy
@import 'icons';
.icon-at {
  background-image: get-icon-at(rgb(187, 68, 68));
  height: 48px;
  width: 48px;
}
.icon-cog {
  background-image: get-icon-cog(#259d78);
  height: 48px;
  width: 48px;
}
.icon-anchor {
  background-image: get-icon-anchor(#387bd0);
  height: 48px;
  width: 48px;
}

Applying these classes to HTML elements yields the following:

Adding backgrounds

This approach can also be used to create circular and square icons as seen in the examples at top by rendering the icons in white (or any color really) and then applying a different background color:

Circular and square icons Copy
// Create a circular icon.
.icon-anchor-circle {
  background-color: #387bd0;
  background-image: get-icon-anchor(#fff);
  background-position: center;
  background-repeat: no-repeat;
  background-size: 60%;
  border-radius: 50%;
  height: 48px;
  width: 48px;
}
.icon-at-square {
  background-color: #b44;
  background-image: get-icon-at(#fff);
  background-position: center;
  background-repeat: no-repeat;
  background-size: 70%;
  border-radius: 0.3rem;
  height: 48px;
  width: 48px;
}

Applying these classes to HTML elements results in the following:

Going further

So far we’ve only specified the fill attribute for the icons, but you can provide additional arguments in your get-icon-x functions to style other attributes such as stroke or stroke-width. Here’s an example of a simple square icon with optional stroke and stroke-width attribute values:

Additional attributes Copy
@import 'icons';
@function get-icon-square ($fill, $stroke: 'none', $stroke-width: 0) {
  $content: '<rect fill="#{$fill}" stroke="#{$stroke}" stroke-width="#{$stroke-width}" height="10" width="10" />';
  @return get-icon($content, '0 0 10 10');
}

Similarly, if you have icons composed of multiple SVG elements you can provide arguments to style each, yielding more complex multi-colored icons.

Limitations of the approach

Adjusting attributes

One potential limitation of the approach is the inability to easily adjust the SVG attributes, e.g., on hover. Instead you have to redefine the entire SVG which could bloat the size of your CSS files if used excessively:

Changing color on hover Copy
@import 'icons';
.icon-cog {
  background-image: get-icon-cog(#259d78);
  height: 48px;
  width: 48px;
}
.icon-cog:hover {
  background-image: get-icon-cog(#307560);
}

When you hover over the cog the SVG background is swapped out like an old-school image swap. There also doesn’t appear to be a way to apply transitions. This is not a problem with the circular and square icons described above as you can simply change the background-color property value.

CSS file sizes

This could be an issue in some applications, though in general can probably be mitigated. If you’re using SVG icons the actual SVG has to make it to the browser one way or the other, may as well be in a CSS file that will likely have a longer caching policy than HTML. Just keep in mind that every time you invoke one of your icon functions in a Sass file you’re injecting the full SVG into the compiled CSS file. You should be able to structure your code so that an icon function is only called once on any given page. For example:

Reusing icons
// DON'T do this:
.icon-anchor {
  background-image: get-icon-anchor(#b44);
  height: 16px;
  width: 16px;
}
.icon-anchor-large {
  background-image: get-icon-anchor(#b44);
  height: 32px;
  width: 32px;
}
// Do this instead:
.icon-anchor {
  background-image: get-icon-anchor(#b44);
  height: 16px;
  width: 16px;
}
.icon-anchor.large {
  height: 32px;
  width: 32px;
}

In the first example “DON’T do this” the SVG is needlessly injected into your CSS files twice. This is avoidable since we’re just changing CSS properties between the regular and large versions of the icon. As noted in the section above, however, if you’re changing SVG attributes you will have to call your get-icon-x function multiple times.

Conclusion

There are a lot of approaches to using SVG icons and the best one is going to depend on your use case. But the above seems to have a lot going for it:

Comments

No comments exist. Be the first!

Leave a comment