The guide to integrating and styling icon systems — SVG sprites, SVG symbols and icon fonts

Sebastiano Guerriero
Nucleo
Published in
16 min readJul 3, 2018

--

In part 1 of our guide on icon systems, we focused on inline SVG and icon components. In this second article, we’ll take an in-depth look at SVG image sprites, SVG symbols and icon fonts.

This guide is the answer to all the requests we receive at Nucleo about how to use the icons exported via the app. The Nucleo icon manager offers plenty of export options to choose from, but it’s not always straightforward which one does what. Until today 😋!

The icon integration methods covered in the guide are:

Part 1
- Inline SVG
- Icon Components

Part 2
- SVG Image Sprites
- SVG Symbols
- Icon Fonts

SVG Image Sprites

An SVG image sprite is an SVG file containing multiple images (e.g., icons). Unlike SVG symbols (more on this technique later), the images included in an SVG sprite are distributed in a grid.

Combining more images in a single file is a performance booster, as opposed to creating a different file for each image. The main reason being a reduction of the HTTP requests. Reducing HTTP requests, in combo with a caching system for your assets, allows your users to download images in no time!

How SVG image sprites work

SVG image sprites behave just like any other static asset. You can store the SVG file in your /assets folder, and set the image in CSS via the URL value of the background-image property.

.icon {
display: inline-block;
background-image: url(../icons.svg);
background-repeat: no-repeat;
}

Setting the URL value is not enough, though. You need to set the size of the basic .icon element, then create a set of classes to target specific icons within the sprite using the background-position property.

Think of it this way: the .icon element is like a mask, it only shows the part of the image sprite that sits right below the mask (the size of the image sprite is bigger than the size of the .icon element). By editing the background position, you decide which icon should be visible.

In code, this translates to:

.icon {
display: inline-block;
height: 96px;
width: 96px;
background-image: url(../icons.svg);
background-repeat: no-repeat;
}
.icon.icon-check {
background-position: -0px -0px;
}
.icon.icon-tags {
background-position: -96px -0px;
}
/* more icon classes here... */

If the negative values of the background position puzzle you, keep in mind that the “0 0” position coincides with the top-left corner of the image. To slide the image to the left (to show a different icon along the X axis), you need to translate the image using negative values. Positive values would push the image to the right, leaving us with an empty box.

In your HTML file you can use a simple snippet, like the one below, to show the icon:

<i class="icon icon-check-all"></i>

Another example would be taking advantage of the ::before pseudo-element to show an icon before some text. Let’s start with the structure:

<button class="btn-download">Download</button>

In CSS:

.btn-download::before {
content: '';
display: inline-block;
height: 16px;
width: 16px;
background-image: url(../icons-16.svg);
background-repeat: no-repeat;
background-position: -16px 0px;
vertical-align: middle;
margin-right: 0.25em;
}

How to generate SVG image sprites

You can generate SVG image sprites (along with the CSS class names), in no time using the Nucleo app! Just import your icons into the app, select them and click on the Export button. In the Export window select the SVG Sprite option and tweak the settings as you please.

Export SVG sprites in Nucleo

Optionally, you can add a secondary color to your icons. This option will generate a clone of each icon with the secondary color applied. You can then use the .active class or simply the :hover selector to modify the color of the icon in CSS.

Pros of using SVG Image Sprites

Building an icon system based on image sprites is easy. Maintaining it (adding new icons, updating the colors of the icons) can be complicated, but luckily tools like Nucleo can help you with that.

Besides reducing the number of HTTP requests, this method allows you to include icons with different styles within the same file; meaning you can have duotone icons along with glyph icons. Optionally, you can include all the vector assets of your project in one image sprite (although this is not always ideal, because the size of the image could cause a delay in showing the vector assets in your web project).

Cons and challenges of using SVG Image Sprites, and how to avoid them using Nucleo or CSS tricks

SVG sprites have some limitations. First of all, you can’t edit colors in CSS, because you don’t have access to the SVG code. In that regard, SVG sprites work just like old PNG sprites (except that SVG sprites are vectors → the device resolution is no longer an issue). There will be times when you need to make color changes though! One option is opening the SVG file in your code editor and replacing the old hexadecimal value with the new one. Alternatively, you can use Nucleo: create a project and add icons to it. Nucleo projects store information about the icons (icon colors, class names etc). If you edit the project by adding/removing icons, or you change the colors, you can simply export the new image sprite, and replace the old SVG and CSS files. The class names won’t change (unless you edit the icon names. Remember: icon names = class names).

Issue number two is adding/removing icons. In this case, the manual editing approach would be tricky (you’d need to translate the icons accordingly and edit the relative background-position). However, now we know we can use Nucleo to export a new SVG sprite and replace the old one.

The real sore spot is scaling the icons. Unlike SVG symbols and icon fonts, image sprites cannot change along with font size (using EM relative units). Using the background-size property to edit the size means recalculating the background-position as well (not ideal). We’re left with the CSS Transform properties:

.btn-download::before {
content: '';
display: inline-block;
height: 16px;
width: 16px;
background-image: url(../icons-16.svg);
background-repeat: no-repeat;
background-position: -16px 0px;
vertical-align: middle;
margin-right: 0.25em;

transform: scale(0.8);
}

This is not always a good solution though: even if you scale up/down the icon, the space it occupies in the flow of the document remains the same. In most cases, this is not acceptable. But I’m afraid this is an issue we have to live with!

One minor nuisance is padding. Specifically, consider you have a sprite where each icon is in a 16x16px square, with no margin. If you want to insert the icon in a 32x32px element, you can’t target the element itself, or you’d see more than one icon (the .icon element would be bigger than the image sprite). The solution in CSS is making the icon children of the element we’re targeting:

<div class="parent">
<i class="icon icon-square"></i>
</div>
.parent {
width: 200px;
height: 200px;
/* center the element using flexbox */
display: flex;
justify-content: center;
align-items: center;
}
.icon {
display: inline-block;
height: 96px;
width: 96px;
background-image: url(../icons.svg);
background-repeat: no-repeat;
}

The final bother with SVG sprites (actually, this applies to all icon systems) is previewing the icons and grabbing the right CSS classes. That means → having to store somewhere the demo files. Luckily, Nucleo can help here too! In your Nucleo projects, you have a gallery where you can preview the icons, and grab the CSS classes via the icon context menu.

Copy SVG sprite class names in Nucleo

Using image sprites in complex projects

To complete this first chapter, I’d like to share a useful technique that can help you create and maintain an icon system based on SVG sprites.

A good icon system is one that makes it easy to find and use icons. This is why a good approach is grouping the icons in different image sprites. It’s important to keep the number of groups small (remember the HTTP requests!), but at the same time split the icons so that 1) it’s easier to know where to find an icon and 2) the size of each image sprite is low.

The two main criteria to create these groups are based on 1) size and 2) style. My favorite one is based on size (icons within the same size range often share similar styles too).

First of all, consider how many “groups” you need. Let’s say we need three groups:
1) icon-sm (small)
2) icon-md (medium)
3) icon-lg (large)

You could even be specific and use, for example, .icon-16 for 16 pixel icons, etc. It’s up to you.

In SCSS, we can define these groups like the following:

$icon-sm: 16px;
$icon-md: 32px;
$icon-lg: 64px;
.icon {
display: inline-block;
background-repeat: no-repeat;
}
@mixin icon-sm {
width: $icon-sm;
height: $icon-sm;
background-image: url(../assets/icons-sm.svg);
}
@mixin icon-md {
width: $icon-md;
height: $icon-md;
background-image: url(../assets/icons-md.svg);
}
@mixin icon-lg {
width: $icon-lg;
height: $icon-lg;
background-image: url(../assets/icons-lg.svg);
}
.icon--sm {
@include icon-sm;
}
.icon--md {
@include icon-md;
}
.icon--lg {
@include icon-lg;
}
// icon classes.icon--check {
background-position: 0 0;
}
.icon--settings {
background-position: -16px 0;
}

Using SCSS variables to define the sizes is optional, it can help in case of bulk changes. The reason why we use mixins for width, height and background-image is to take into account cases where we don’t want to/can’t apply the .icon class (see the example above where we use the pseudo-element).

In HTML:

<i class="icon icon--sm icon--check"></i>

SVG Symbols

I love this method because it combines the simplicity of icon fonts (copy and paste snippets to show the icons), with the power of inline SVG (edit colors, stroke values, etc.). The SVG symbol is, in a way, an SVG sprite: an SVG file containing all icons. In this case, though, the icons are not arranged in a grid; the position of the icons does not matter.

This technique is based on the use of two elements: <symbol> and <use>.

The <symbol> element is used to group elements together; it is never displayed, but it instead defines a template which can be rendered using a <use> element.

How SVG symbols work

Let’s consider the code of an SVG icon:

<svg width="48" height="48" viewBox="0 0 48 48">
<title>skull</title>
<path d=".." fill="none" stroke="#444" stroke-width="2"/>
</svg>

We know that if we paste this code into our document, the icon is visible. However, if we wrap the content of the icon inside a <symbol> element

<svg style="display: none;">
<symbol viewBox="0 0 48 48" id="skull">
<title>skull</title>
<path d=".." fill="none" stroke="#444" stroke-width="2"/>
</symbol>
</svg>

and we insert this code into our document, the icon is no longer visible; that’s because we need to reference it using the <use> element. Somewhere else in our document (where we want to place the icon), we need to use the following snippet:

<body>
<svg><use href="#skull" xlink:href="#skull"/></svg>
</body>

(Note that even though xlink:href has been deprecated, the href attributed inside an SVG element is not supported in Safari 11. Once the SVG 2 specification is supported by all browsers, you can safely remove xlink:href.)

To recap: once you have grouped each icon (using the <symbol>), you can display it as many times as you want using the <use> element. To specify which icon you want to display, use the href attribute, whose value is the ID of the <symbol> element.

You may have noticed that we added a style=”display: none;” to the SVG wrapping the <symbol> element: even if the <symbol> is not displayed, the <svg> element wrapping it is still rendered and will take up some space in our page, and this is why we need to hide it.

How to generate SVG symbols

To create the SVG file that contains all your icons, group each icon into a <symbol> element.

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol viewBox="0 0 48 48" id="skull">
<title>skull</title>
<path d=".." fill="none" stroke="#444" stroke-width="2"/>
</symbol>
<symbol viewBox="0 0 32 32" id="check">
<title>check</title>
<path d=".." fill="none" stroke="#444" stroke-width="2"/>
</symbol>
<symbol viewBox="0 0 48 48" id="home">
<title>home</title>
<path d=".." fill="none" stroke="#444" stroke-width="2"/>
</symbol>
</svg>

Each <symbol> element needs to have an ID; this ID will be used to reference it inside your document using <use>.

Note that we have specified a viewBox attribute for each <symbol> element. The viewBox attribute defines the aspect ratio of your icon. It is composed of 4 numbers, the first 2 are usually zero (but it really depends on how the icon was drawn), while the other 2 are the width and height of the icon. If you’re not familiar with the viewBox attribute, you should take a look at this article about scaling SVG.

Your icons do not need to have the same size since you can define a different viewBox attribute for each one of them.

Finally, it’s good practice to add a <title> tag in each SVG icon to improve accessibility.

Now that our SVG file is ready, where do we store it? Option 1 would be including the SVG code at the top of the document, right after the <body> element. Ideally, you can create a component, or a partial, to store the icons:

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="assets/css/style.css">
<title>Title</title>
</head>
<body>
{% include "_icons.nunjucks" %}

Once again, to display one of your icons you need to insert the following snippet somewhere in your document:

<svg><use href="#skull" xlink:href="#skull"/></svg>

This method is supported by all modern browsers (IE9+).

Option 2 would be storing the icons.svg file (containing all your icons) in your assets folder. This is referred to as “using an external reference”. If you do so, the snippet used in your document changes a bit:

<svg><use href="assets/icons.svg#skull" xlink:href="assets/icons.svg#skull"/></svg>

The value of the href attribute needs to include also the external reference.

This method is not supported by Internet Explorer (I know, right?). However, you can use a polyfill like svgxuse to fix the issue with the old IE friend.

Creating and maintaining SVG symbols can be a lot of work. Luckily, tools like Nucleo can generate and update these files in no time!

Export SVG symbols in Nucleo

Styling SVG symbols

Styling SVG <use> elements can be a little tricky. That’s because SVG icons referenced this way have their own separate DOM (know as Shadow DOM) which is not directly accessible by CSS selectors.

For example, using the following code in CSS:

svg.icon path {
fill: red;
}

won’t work with SVG symbols (while we know it would work with inline SVG).

To solve this issue, we have to optimize the code of the icons; make it “customizations-friendly”.

For example, if we consider this icon:

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol viewBox="0 0 48 48" id="skull">
<title>skull</title>
<path d=".." fill="#444"/>
</symbol>
</svg>

We can remove the fill value from the path element, and define it in CSS. This time it works because the style defined in CSS won’t be overwritten by the inline style of the icon.

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol viewBox="0 0 48 48" id="skull">
<title>skull</title>
<path d=".." />
</symbol>
</svg>

in CSS

svg.icon {
fill: red;
}

Icons can be more complicated, of course. Their SVG code can include groups, with different fill (and stroke) values applied to them and their children. Therefore, the process of optimizing the code is not always so simple. As a general rule, you need to remove from the code of the icons all the presentation attributes that you wish to modify in CSS.

At Nucleo, we’ve developed a method that allows us to apply style to all icons regardless of their complexity. If you want to learn more about it, take a look at Part 1 of this guide (the Inline SVG section).

Pros and Cons of using SVG symbols

SVG symbols represent an excellent choice for building an icon system. Compared to icon fonts, for example, you can customize the icons even further (e.g., edit all the colors of the icons, or the stroke values). You can also replace the color values of the icons with CSS custom properties, and edit them in CSS (kudos to Sarah Dayan).

On top of that, the technique is very simple (and similar to working with icon fonts). You only need to get used to copying and pasting SVG icons as opposed to applying classes to other elements of the document.

I can’t find real cons related to this method. A minor inconvenience could be that you cannot set icons as background images in CSS. For example, if you have an unordered list:

<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>

And you want to replace all the bullet points with the same icon, you can’t use the ::before pseudo-element like you would with icon fonts. You have to insert the same icon in each list item.

<ul>
<li><svg><use href="#custom-bullet"/></svg>Item 1</li>
<li><svg><use href="#custom-bullet"/></svg>Item 2</li>
<li><svg><use href="#custom-bullet"/></svg>Item 3</li>
<li><svg><use href="#custom-bullet"/></svg>Item 4</li>
</ul>

However, this problem can be avoided using a component-based framework. And I bet you do use one. 😉

One other minor issue (once again, this applies to all icon systems), is having to deal with a demo file. Enter Nucleo! With our app, you can create a project, then export the project as SVG <symbol>. The app generates a demo file just in case, but you can ignore it and use the app instead! Via the icon context menu, you can copy the code snippets of the icons. Besides, the app generates all the CSS files you need to customize the icons, as well as the SVG file containing all the icons! Oh and, bonus point, you can even import SVG symbols, and the app will generate the single SVG files for you. 🙌

Copy icon snippets directly in Nucleo

Using SVG symbols in complex projects

The technique of including icons in your document does not change with the complexity of your project. However, there are a few tricks you can use in your CSS to speed up your icon workflow.

For example, you can set some utility classes to modify the size of your icons:

.icon {
display: inline-block;
height: 1em;
width: 1em;
}
/* --------------------------------
Change icon size
-------------------------------- */
.icon-xs {
height: 0.5em;
width: 0.5em;
}
.icon-sm {
height: 0.8em;
width: 0.8em;
}
.icon-lg {
height: 1.6em;
width: 1.6em;
}
.icon-xl {
height: 2em;
width: 2em;
}

Or you can set color themes:

.icon {
display: inline-block;
/* icon primary color */
color: #111111;
fill: currentColor;
height: 1em;
width: 1em;
}
.icon-light {
color: #fff;
}

Icon Fonts

Last but not least, comes the most popular method for embedding and styling icons: icon fonts. In short, this method consists of embedding the icons as font files and using CSS classes to target specific icons.

How icon fonts work

To include your font, you can use the @font-face CSS rule (or you can embed the fonts as Base64 data strings):

@font-face {
font-family: 'Nucleo';
src: url('../fonts/Nucleo.eot');
src: url('../fonts/Nucleo.eot') format('embedded-opentype'), url('../fonts/Nucleo.woff2') format('woff2'), url('../fonts/Nucleo.woff') format('woff'), url('../fonts/Nucleo.ttf') format('truetype'), url('../fonts/Nucleo.svg') format('svg');
font-weight: normal;
font-style: normal;
}

A list of CSS classes is used, along with a base .icon class, to target specific icons:

/*------------------------
base class definition
-------------------------*/
.icon {
display: inline-block;
font: normal normal normal 1em/1 'Nucleo';
speak: none;
text-transform: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/*------------------------
icons
-------------------------*/
.icon-aubergine::before {
content: "\ea01";
}
.icon-bag-delivery::before {
content: "\ea02";
}
.icon-bakery::before {
content: "\ea03";
}
.icon-blueberries::before {
content: "\ea04";
}

In your document, you can use this simple snippet to display an icon:

<i class="icon icon-aubergine"></i>

How to generate icon fonts

There are plenty of tools that generate icon fonts from single SVG files. Our favorite one is (guess what?) Nucleo. Our app can import/export icon fonts, and create the font files and the CSS files needed to use them.

Some advantages of using Nucleo to create icon fonts:
- if you generate an icon font from a Nucleo project, you can use the app to retrieve CSS class names (bye bye demo files!).
- You can edit class names in Nucleo by editing the names of the icons.
- You can export fonts also from icons with stroke values.
- If you use a Nucleo project, the app stores an index of the icons, therefore you can add and remove icons, and each time you export a new font you can just replace the old files with the new ones.

Export icon fonts in Nucleo

Pros and Cons of using icon fonts

This method has been around for so long, mostly for its simplicity of use. There are some concerns about accessibility, but I’m not diving into these (there are many techniques you can use to improve icon fonts accessibility, mostly related to using aria attributes → e.g., the aria-hidden=”true” attribute can be applied if you don’t want screen readers to “read” the icon).

In general, one limitation is that you can’t apply more than one color to the icons, or edit the stroke values.

Also, you need a demo file to preview the icons and copy the CSS class names. By this point, we all know we can replace the demo file with the Nucleo app.

In conclusion, icon fonts remain a good technique for embedding icons in your design system.

Using icon fonts in complex projects

The method of including icons as fonts does not change if the project is more complicated. We can take advantage of some useful CSS utility classes, and that’s pretty much all we can do.

For example, we can create CSS classes for editing the size of the icons

/* relative units */
.icon-sm {
font-size: 0.8em;
}
.icon-lg {
font-size: 1.2em;
}
/* absolute units */
.icon-16 {
font-size: 16px;
}
.icon-32 {
font-size: 32px;
}

or we can create a class for a “spinning/loader” effect:

/*------------------------
spinning icons
-------------------------*/
.icon-is-spinning {
animation: icon-spin 2s infinite linear;
}
@keyframes icon-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

If needed, you can use other utility classes to rotate/flip icons, add frames, use icons as list item markers, etc. It’s really up to you.

Conclusion

I hope this guide provided some practical information that’ll help you with your projects. Any feedback on the methods explained in the article is more than welcome! For more tutorials on icon design and icon systems, make sure to follow us here on Medium or Twitter

--

--