Skip to content

How to create a SVG icon system

Wherein I describe a pretty nice workflow for creating and maintaining an SVG icon set that will make it easy to tweak/extend your set of icons in the comfort of your favorite vector graphics application (as long as your favorite vector graphics application happens to be Adobe Illustrator) while at the same time providing the tools to generate the SVG assets automatically.

The gist:

  1. Keep your icons in a single .AI file with multiple artboards, and export each artboard as SVG;
  2. Use grunt-svgstore to clean up and merge the SVG files and grunt-svginjector to generate a JavaScript file that you can use to inject the SVGs into your page;
  3. Write minimal CSS to lay out and color your icons;
  4. Enjoy the awesomeness of crisp, responsive icons.

Let's do this.

The case for SVG icons

People in the ongoing discussion around the best way to use icons in websites and web apps tend to agree that SVG is the optimal solution in many cases. I won't cover it here, but Chris Coyier's article explains well why we want to define our icons as named <symbol> elements that we can then reference like this:

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

Design your icons

To work on your icons side by side and have them neatly packaged in a single .AI file, use multiple artboards--one for each icon in your set.

In the New Document screen, enter the number of artboards and choose other sensible settings (e.g. ✓ Align New Objects to Pixel Grid)

In the Artboards pane (if it's not shown, go in and check Window → ✓ Artboards) you can add, remove and rearrange the artboards in your file. Make sure each artboard has the appropriate name, as this will ultimately become the name of your icon in your icon set.

Pro tip interlude

Whenever possible, it's a good idea to make your icon a single compound path, which basically takes the various <path>, <rect>, <polygon> etc. elements and merges them into a single <path>. This gives you a slimmer SVG and you'll avoid Firefox rendering quirks — I've seen it antialias some of the paths and leave others aliased and it just makes the icon look half-assed which is super depressing.

In Illustrator, you create a compound path by selecting your shapes and hitting ⌘8 on your keyboard. Use ⌥⇧⌘8 to release a compound path if you find you need to make changes to individual paths. Both actions are accessible under Object → Compound Path.

Export time!

Fast-forward to the moment you're happy with the set of icons and/or working inside Adobe Illustrator becomes unbearable and it's time to export those babies as separate SVG files. Granted it's a bit obtuse, since we piggyback off the Save a copy... functionality to obtain that which might more accurately be described as an export, but I haven't found a more straightforward way to do it:

File → Save a copy… → Format: SVG (svg), Use artboards: All

On the SVG Options screen the default settings (i.e. Profile: SVG 1.1) are fine. Each artboard will be saved as a separate SVG file with the name fileName_artboardName.svg.

Clean up and package the icons

Next, we take all the little individual SVG files and package them into a single SVG file. For that we will need Grunt and a plugin called grunt-svgstore. If you haven't used Grunt before, you can check out this short introduction.

Here's how our Gruntfile should look:

module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-svgstore');
grunt.initConfig({
svgstore: {
icons: {
files: {
'icons.svg': ['icons/*.svg']
},
options: {
/*
prefix all icons with an unambiguous label
*/

prefix: 'icon-',

/*
cleans fill, stroke, stroke-width attributes
so that we can style them from CSS
*/

cleanup: true,

/*
write a custom function to strip the first part
of the file that Adobe Illustrator generates
when exporting the artboards to SVG
*/

convertNameToId: function (name) {
return name.replace(/^\w+\_/, '');
}
}
}
}
});
};

Next we need a way to include the icons.svg file in our web page. Rather than copying the content and inlining it in our HTML, we can include it via a <script> tag (making it easier to maintain and a candidate for browser caching), for which we can use grunt-svginjector:

module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-svgstore');
grunt.loadNpmTasks('grunt-svginjector');

grunt.initConfig({
svgstore: {
icons: {
files: {
'icons.svg': ['icons/*.svg']
},
options: {
cleanup: true,
convertNameToId: function (name) {
return name.replace(/^\w+\_/, '');
}
}
}
},
svginjector: {
icons: {
files: {
'icons.js': ['icons.svg']
},
options: {
container: 'icon-container'
}
}
}
});

grunt.registerTask('icons', ['svgstore:icons', 'svginjector:icons']);
};

In our HTML we need a container into which we'll inject the content of icons.svg and then we can just include the script:

<div id="icon-container"></div>
<script src="icons.js"></script>

Note: Since the script inserts the content immediately, you must make sure the script is always included after the container in your DOM.

We've registered a task called icons to execute the svgstore and svginjector steps in order, so we can run:

$ grunt icons

...to prep our icons for usage.

Use and customize your icons

Some final touches and we're done. Firstly, we need to hide the icon container so it does not take space in the layout.

Remember that we set cleanup: true in grunt-svgstore to strip all presentational attributes like fill, stroke or stroke-width. This is a necessity if we want to control these attributes from our CSS, since no attribute defined inside the <symbol> elements can be overridden when we <use> them. To make sure everything looks okay, we'll define a stroke and a fill for the icons.

/* 
Hide the icon container, because the injected SVG
takes up physical space.
*/

#icon-container {
display: none;
}

/*
Set up the size, layout and colors.
*/

.icon {
display: inline-block;
width: 2em;
height: 2em;
}

.icon use {
stroke: #000;
fill: none;
}

Then, as promised, whenever you need to use an icon on your web page:

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

Easy.

Further reading