How to create an icon system using SVG symbols

export SVG symbols

When it comes to implementing an icon system for your web projects, SVG is a great choice! There are few different ways to create an icon system using SVG, though. In this article, we'll take a look at one of them: SVG symbols. 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 rather defines a template which can be rendered using a <use> element.
Let's consider the code of an SVG icon created using Illustrator:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24">
	<path fill="#E86C60" d="M17,0c-1.9,0-3.7,0.8-5,2.1C10.7,0.8,8.9,0,7,0C3.1,0,0,3.1,0,7c0,6.4,10.9,15.4,11.4,15.8 c0.2,0.2,0.4,0.2,0.6,0.2s0.4-0.1,0.6-0.2C13.1,22.4,24,13.4,24,7C24,3.1,20.9,0,17,0z"></path>
</svg>

and let's wrap its content inside a <symbol> element:

<svg>
	<symbol viewBox="0 0 24 24" id="heart">
		<path fill="#E86C60" d="M17,0c-1.9,0-3.7,0.8-5,2.1C10.7,0.8,8.9,0,7,0C3.1,0,0,3.1,0,7c0,6.4,10.9,15.4,11.4,15.8 c0.2,0.2,0.4,0.2,0.6,0.2s0.4-0.1,0.6-0.2C13.1,22.4,24,13.4,24,7C24,3.1,20.9,0,17,0z"></path>
	</symbol>
</svg>

Now if we insert this code into our page, we'll see that the icon is not visible, and that's because we need to reference it using the <use> element:

<body>
	<svg style="display: none;">
		<symbol viewBox="0 0 24 24" id="heart">
			<path fill="#E86C60" d="M17,0c-1.9,0-3.7,0.8-5,2.1C10.7,0.8,8.9,0,7,0C3.1,0,0,3.1,0,7c0,6.4,10.9,15.4,11.4,15.8 c0.2,0.2,0.4,0.2,0.6,0.2s0.4-0.1,0.6-0.2C13.1,22.4,24,13.4,24,7C24,3.1,20.9,0,17,0z"></path>
		</symbol>
	</svg>

	<svg>
		<use xlink:href="#heart"/> <!-- this is our visible icon -->
	</svg>
</body>

And this is the result:

svg-heart

In other words, once you have defined a group of graphic objects (using the <symbol>), you can display it as many times as you want using the <use> element. You specify the group you want to display using the xlink:href attribute which, in our case, is the ID of the <symbol> element we want to display (#heart).

You may have notice 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 your page, and this is why we need to hide it.

Now that we know what the <symbol> and the <use> elements are and how they work, let's build our SVG sprite.
First, you need to have all your icons, each one in a separate .svg file. You can then create a new (empty) .svg file (I'm going to call it myicons.svg).
Inside this new file, insert a <svg> tag and then, for each one of your icons, a <symbol> element which will wrap the icon code.

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
	<symbol viewBox="0 0 24 24" id="heart">
		<path fill="#E86C60" d="M17,0c-1.9,0-3.7,0.8-5,2.1C10.7,0.8,8.9,0,7,0C3.1,0,0,3.1,0,7c0,6.4,10.9,15.4,11.4,15.8 c0.2,0.2,0.4,0.2,0.6,0.2s0.4-0.1,0.6-0.2C13.1,22.4,24,13.4,24,7C24,3.1,20.9,0,17,0z"></path>
	</symbol>

	<symbol viewBox="0 0 32 32" id="arrow">
		<path fill="#0f0f0f" d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z M22.8,13.6l-6,8C16.6,21.9,16.3,22,16,22 s-0.6-0.1-0.8-0.4l-6-8c-0.2-0.3-0.3-0.7-0.1-1S9.6,12,10,12h12c0.4,0,0.7,0.2,0.9,0.6S23,13.3,22.8,13.6z"></path>
	</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 SVG (if you're not familiar with the viewBox attribute, you should take a look at this article about scaling SVG).
This way your icons do not need to have the same aspect ratio since you can define a different viewBox attribute for each one of them.

As a final step, we can add a <title> tag to each SVG icon to improve its accessibility:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
	<symbol viewBox="0 0 24 24" id="heart">
		<title>Heart</title>
		<path fill="#E86C60" d="M17,0c-1.9,0-3.7,0.8-5,2.1C10.7,0.8,8.9,0,7,0C3.1,0,0,3.1,0,7c0,6.4,10.9,15.4,11.4,15.8 c0.2,0.2,0.4,0.2,0.6,0.2s0.4-0.1,0.6-0.2C13.1,22.4,24,13.4,24,7C24,3.1,20.9,0,17,0z"></path>
	</symbol>

	<symbol viewBox="0 0 32 32" id="arrow">
		<title>Arrow</title>
		<path fill="#0f0f0f" d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z M22.8,13.6l-6,8C16.6,21.9,16.3,22,16,22 s-0.6-0.1-0.8-0.4l-6-8c-0.2-0.3-0.3-0.7-0.1-1S9.6,12,10,12h12c0.4,0,0.7,0.2,0.9,0.6S23,13.3,22.8,13.6z"></path>
	</symbol>
</svg>

Our SVG sprite is now ready to be used! You can save the myicons.svg file inside your assets folder (I'm going to call this folder img).
To display one of your icons, all you need to do is inserting the following snippet somewhere in your document:

<svg>
	<use xlink:href="img/myicons.svg#heart"/>
</svg>

That’s it!

What about browser compatibility? Unfortunately, referencing external SVG in <use> doesn’t work in Internet Explorer, not even IE9+ (luckily, this issue has been solved in Microsoft Edge).

How to fix this? Let's have a look at two possible solutions.
1) We can include the SVG sprite at the top of the document, and then reference each icon using, again, the <use> tag:

<svg style="display: none;"> <!-- this is our svg sprite -->
	<symbol viewBox="0 0 24 24" id="heart">
		<path fill="#E86C60" d="M17,0c-1.9,0-3.7,0.8-5,2.1C10.7,0.8,8.9,0,7,0C3.1,0,0,3.1,0,7c0,6.4,10.9,15.4,11.4,15.8 c0.2,0.2,0.4,0.2,0.6,0.2s0.4-0.1,0.6-0.2C13.1,22.4,24,13.4,24,7C24,3.1,20.9,0,17,0z"></path>
	</symbol>

	<symbol viewBox="0 0 32 32" id="arrow">
		<!-- ... -->
	</symbol>
</svg>

<svg>
	<use xlink:href="#heart"/> <!-- this is our visible icon -->
</svg>

Note that this time the xlink:href attribute is equal to the ID identifier only (it does not include the external source).
This technique works great but has the downside of the SVG sprite not being cached.

2) We can use a polyfill; a good example is svgxuse.  This polyfill 'fetches external SVGs referenced in <use> elements when the browser itself fails to do so.’ Basically, the polyfill goes through every <use> element and, if it is referencing an external SVG that the browser has failed to load, grabs the SVG and prepend it to the <body> of your document. Great!
You can download it from the GitHub repo, include it in your document, and you’re good to go!

Note: remember that SVG are supported in IE9+ only; so if you still need to support IE8 (and below), you should use a fallback (for example, png images).

Now, what about styling the SVG? Well, styling SVG <use> elements could be a little tricky. That’s because SVG icons referenced this way have their own separate DOM (know as Shadow DOM) which is not accessible by CSS selectors. Let's say we have our icon:

<svg class="icon">
	<use xlink:href="img/myicons.svg#heart"/>
</svg>

Doing something like that:

.icon path {
	fill: #000000;
}

won’t work.

How to solve this problem? Let’s say you want to change the fill color of your icons. First, make sure the fill attribute is not defined inline.
So, if this is your icon sprite:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
	<symbol viewBox="0 0 24 24" id="heart">
		<title>Heart</title>
		<path fill="#E86C60" d="M17,0c-1.9,0-3.7,0.8-5,2.1C10.7,0.8,8.9,0,7,0C3.1,0,0,3.1,0,7c0,6.4,10.9,15.4,11.4,15.8 c0.2,0.2,0.4,0.2,0.6,0.2s0.4-0.1,0.6-0.2C13.1,22.4,24,13.4,24,7C24,3.1,20.9,0,17,0z"></path>
	</symbol>

	<symbol viewBox="0 0 32 32" id="arrow">
		<title>Arrow</title>
		<path fill="#0f0f0f" d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z M22.8,13.6l-6,8C16.6,21.9,16.3,22,16,22 s-0.6-0.1-0.8-0.4l-6-8c-0.2-0.3-0.3-0.7-0.1-1S9.6,12,10,12h12c0.4,0,0.7,0.2,0.9,0.6S23,13.3,22.8,13.6z"></path>
	</symbol>
</svg>

you need to remove the inline fill attribute, like that:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
	<symbol viewBox="0 0 24 24" id="heart">
		<title>Heart</title>
		<path d="M17,0c-1.9,0-3.7,0.8-5,2.1C10.7,0.8,8.9,0,7,0C3.1,0,0,3.1,0,7c0,6.4,10.9,15.4,11.4,15.8 c0.2,0.2,0.4,0.2,0.6,0.2s0.4-0.1,0.6-0.2C13.1,22.4,24,13.4,24,7C24,3.1,20.9,0,17,0z"></path>
	</symbol>

	<symbol viewBox="0 0 32 32" id="arrow">
		<title>Arrow</title>
		<path d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z M22.8,13.6l-6,8C16.6,21.9,16.3,22,16,22 s-0.6-0.1-0.8-0.4l-6-8c-0.2-0.3-0.3-0.7-0.1-1S9.6,12,10,12h12c0.4,0,0.7,0.2,0.9,0.6S23,13.3,22.8,13.6z"></path>
	</symbol>
</svg>

Now in your css code, add:

.icon {
	fill: #00000; /* this will be your icons default color */
}

Since you have not specified a fill color for the path elements inside your SVG, they will inherit the fill color of the parent SVG, that you can modify using CSS selectors.

Now, what if we want to change the fill color of one icon only? We can target this icon using a class:

<svg class="icon my-class-name">
	<use xlink:href="img/myicons.svg#heart"></use>
<svg>

and then change its fill value using css:

.my-class-name {
	fill: red;
}

Create your SVG symbol sprite with Nucleo

Creating an SVG symbol sprite manually could be tiresome, particularly if you have quite a few icons. Nucleo automates the process of creating SVG symbol sprites so that you don't have to do any manual job!

From the web app, select the icons you want to download and click the Download button. In the settings popup, check the ‘Export as <symbol>' option and insert the path to your assets folder (this will be used to set a proper xlink:href value for the <use> elements); insert a file name (this will be the name of your SVG sprite file). Then click Save. That's it! Your sprite is ready to be used!

export svg symbols

The folder you just downloaded will contain:

Basically, this demo file lists all the icons you downloaded and easily allows you to include them in your document: simply click on the icon you want to include, this will select the snippet you need to copy and paste in your document to show the selected icon. That’s it!

demo file

The icons will be shown with the style you selected in the app.

What about styling a single icon? You can target your icon with a class:

<svg class="nc-icon grid-32 glyph my-class-name">
	<use xlink:href="img/myicons.svg#double-left"/>
</svg>

and then modify it using css:

.my-class-name {
	color: purple;
}

Great! But wait, Nucleo icons are bi-color… what if I want to change the second color? Easy peasy, just add:

.my-class-name use {
	color: orange;
}

Here you go!

Our symbols come with some additional tricks.

  1. Our outline icons have customizable stroke. What if you want to change the stroke-width of one icon? Let’s say you downloaded an icon with a stroke-width of 2px, then the stroke-2 class is automatically added to the SVG element. If you want to switch to a stroke of 3px, just replace the stroke-2 class with a stroke-3 class and you are good to go!
  2. What about aligning icons to the text? In the demo file, at the top, switch to ‘align to text’; then just copy your SVG snippet and paste it in you document (kudos to Cloud Four for the 'align to text' style).
    Note that, in this case, the SVG will inherit the color of the text; but, again, you can easily customize it using a custom class, as we did before.
  3. If you prefer to include your SVG sprite inside your document (rather than referencing an external SVG), uncheck the 'Reference external SVG' option (top of the demo file); this will automatically set the xlink:href attribute to be equal to the <symbol> ID identifier only.

Using SVG symbols is a really smart way to manage your icons; that's why we included the 'symbol export' feature in Nucleo.
Managing your icons with Nucleo is really effortless, even if you're working on multiple projects: all you need to do is create a project, add icons to it and then download them in just a couple of clicks!

  • http://fvsch.com/ fvsch

    Thanks for sharing. I have one small warning and a small question.

    1) Warning: Be careful with this code:

    svg { fill: #000000; }

    Firefox will apply it to the SVG elements it inserts inside USE elements (in its shadow DOM), so you’re basically declaring it on the root element of the icon and on its grandchild. So if you have this code:

    svg { fill: #000000; }
    .my-icon { fill: blue; }

    In most browsers the icon will be blue, but in Firefox it will be black. That’s a bug but sadly it’s unlikely to get fixed quick. So you need a more precise selector (e.g. targetting the nc-icon class in your examples). Or you need to fix the problem for Firefox:

    svg { fill: currentColor; /* currentColor is the new black */ }
    use > svg { fill: inherit; }

    2) Question: Regarding 2-color icons, can you describe how having one currentColor on the parent SVG element and another currentColor on the USE element results in those two colors being used as fills for the icon’s paths? I’ve never seen that technique before. (The only technique I know is combining fill+color in CSS, and using fill=”currentColor” on some of the paths.)

    • http://codyhouse.co Claudia Romano

      Hi there!
      1) Fixed it using .icon class (rather than svg). Thanks for the heads-up! ;)

      2) Basically the technique is the same as the one you described. About Nucleo icons: we have both outline (icons with stroke but not fill) and glyph icons (with fill but not stroke). To change their color, you need to change the stroke color for the first and the fill color for the latter. But we wanted to provide our users with a way to change the color of the icons, without worrying about the property they actually have to change. And that’s why we came out with this slight different technique. The concept is exactly the same: we remove the fill/stroke inline attribute from the svg symbol; in the css we add:

      .nc-icon.glyph {
      fill: currentColor;
      stroke: none;
      }

      .nc-icon.outline {
      stroke: currentColor;
      fill: none;
      }

      Now this will change the color of all you elements:

      .nc-icon {
      color: #0f0f0f;
      }

      But, if you add inline a fill=”currentColor” (or stroke=”currentColor”) to a specific path, then you can target it using the color:

      .nc-icon use {
      color: #b30404;
      }

      Again, it’s basically the same technique you described, it just easily handles both icons with stroke or fill.
      Hope this was clear!

  • fsidc

    Too much trouble, you can download this free svg icon.
    https://www.onlinewebfonts.com/icon/

  • Eriko Lima

    Hi Claudia, thanks for your article, i have a question, what about SVG images that have multiple paths, should I add the G inside the , I tried and didn’t work, i’m not sure why.