Component based SVG Icon System
Much has been written about SVG Icon Systems and why they are amazing. In this post I want to share my workflow for creating a component based SVG icon system.
The focus here is on front-end JavaScript frameworks such as React, Angular, Vue.js, etc. These frameworks allow us to split the UI into discrete, reusable components. Which means we can create a generic icon component. And use the type
property to render inline the appropriate icon.
<!-- React -->
<Icon type="cloud-with-snow" className="f4 blue" />
<!-- Angular -->
<svg icon type="cloud-with-snow" class="f4 blue"></svg>
<!-- Vue.JS -->
<icon type="cloud-with-snow" class="f4 blue"></icon>
Prepare SVG Files
Our starting point will be .svg
files – one per icon. In most cases these will be generated using an app such as Illustrator or Sketch, although you can also obtain them from an icon pack, for example: Material Design icons, iconmonstr, SVG Icons, etc.
I like to start by running all the files through GitHub - svg/svgo or SVGOMG. This not only optimizes the SVG but also strips out any editor artifacts and tries to reduce it to a single <path>
.
Take a moment to visually check all your icon files. You might have to play with the SVGO settings, especially the precision setting, to ensure that the optimized version does not get distorted.
Next we are going to make a couple of modifications to all the .svg
files.
Ensure that all the icons use
viewBox
and remove anywidth
orheight
attributes. You can configure SVGO to do this for you automatically. This will make it easier to control the size of the icon.Set the
fill
andstroke
(or whichever of the two you are using) tocurrentColor
. This sets the icon colour to be the same as the surrounding text. We can then control this colour by setting thecolor
property on theicon
element.
At this point the SVG files should look something like this:
<svg viewBox="0 0 20 20">
<path fill="currentColor" d="..." />
</svg>
Generate the Sprite Sheet
One of the more popular techniques for implementing an icon system is to use <symbol>
and <use>
elements.
An improvement is to use the
— Chris Coyier (SVG symbol a Good Choice for Icons)<symbol>
element in SVG instead of directly referencing shapes (or a<g>
), because you can define theviewBox
directly on the<symbol>
and then not need one when you<use>
it later in an<svg>
.
The <symbol>
element allows us to define an SVG template. It is never displayed. Therefore, we can use it to create an icon sprite sheet.
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
style="position:absolute;width:0;height:0;visibility:hidden"
>
<defs>
<symbol id="sun" viewBox="0 0 20 20">
<path fill="currentColor" d="..." />
</symbol>
<symbol id="moon" viewBox="0 0 20 20">
<path fill="currentColor" d="..." />
</symbol>
</defs>
</svg>
We can then render an icon using the <use>
element.
<svg>
<use xlink:href="moon" />
</svg>
If you are working with React or Angular or Vue.js then there is a good chance that your project was setup using webpack. We will use the svg-sprite-loader for webpack to convert a folder full of SVG files into an icon sprite sheet.
const files = require.context('!svg-sprite-loader!./assets', false, /.*\.svg$/);
files.keys().forEach(files);
To do so:
We are using
require.conext
to generate a list of SVG files in theassets
folder.We then iterate over this list and load all the files using
svg-sprite-loader
.svg-sprite-loader
then generates the sprite sheet and injects it into DOM on run-time. Similar to how style-loader works.
⚠️ Not using webpack? There are also node, gulp or grunt based tools that you can use as an alternative.
Icon Component
Time to put everything together and build the icon component. Below is the React version of the component – the Angular and Vue.js versions are quite similar. For the <svg>
element I have set display
to inline-block
and verticalAlign
to middle
. I am using tachyons for styling here; however, you can set those styles using any technique.
import React from 'react';
const files = require.context('!svg-sprite-loader!./assets', false, /.*\.svg$/);
files.keys().forEach(files);
const Icon = ({ type, className }) => (
<svg className={`dib v-mid ${className}`} width="1em" height="1em">
<use xlinkHref={`#${type}`}></use>
</svg>
);
export default Icon;
The width
& height
attributes are set to 1em
. If needed, adjust the values based on the aspect ratio of your icons. This will give us more flexibility to control the size of the icon.
The component itself has two props:
type
: to pick which icon needs to be rendered.className
: to allow us to add more CSS classes to<svg>
element.
The following will render a cloud-with-snow
icon.
<Icon type="cloud-with-snow" />
Colour
We can control the colour of the icon by using the font-color
. The following will render a green rainbow
icon.
<Icon type="rainbow" className="green" />
Size
We have two options for setting size of the icon:
Using font-size
: this works great when you are rendering the icon next to some kind of text. For example, in a paragraph or a button. Because we set the width
& height
attributes to 1em
the icon scales to match the font size. The following will render a blue wind
icon that is 1.25rem
tall.
<Icon type="wind" className="f4 blue" />
In some scenarios you might want to set the size of the icon manually instead of relying on font-size
. SVG attributes have the lowest specificity so, you can always override them using CSS. The following will render a yellow orbit
icon that is 4rem
wide and tall.
<Icon type="orbit" className="w3 h3 yellow" />
Usage with Base Element
The <base>
element specifies the base URL to use for resolving all the relative URLs in an HTML document. On some browsers this prevents the icons from rendering 😞 We can fix this by using absolute values for xlinkHref
.
const baseUrl = window.location.href.replace(window.location.hash, '');
const xlinkHref = baseUrl + `#${type}`;
I recently encountered this issue when working with the Angular router. It relies on the <base>
element being set. However, you can provide APP_BASE_HREF
instead and still use the router and the SVG sprite sheet.