Tell me if this sounds familiar: You have a bunch of icons to use in your awesome web project. You don't want to use an icon library (Font awesome, material icons) probably because they're from different sources, or you just want to avoid the huge size that comes with libraries. So you head to icomoon.io (or similar) and upload all of your icons and then generate and download the icon-font set (or SVG sprite) generated for you and you go ahead to use that in your project.
The problem is, in 2 weeks' time, your designer comes along with a bunch of other icons you have to add and you have to upload these new ones, a JSON file from the one you generated earlier, and then generate a new set. This can get pretty monotonous and annoying, and there are a couple of ways things can go wrong; oh and did I mention, boring and monotonous.
In this article, we're going to look at:
- how we can automate that process. Such that, whenever you need to add a new icon, you just run a command and your new SVG Sprite is generated for you
- creating a reusable component to use the generated icons in the SVG Sprite.
This article shows how to create reusable SVG icons using SVG sprites (you should be using (controversially) SVG sprites as opposed to icon fonts. Here's why: https://css-tricks.com/icon-fonts-vs-svg/, https://www.sitepoint.com/icon-fonts-vs-svg-debate/). However, the same principles can be applied to generating and using icon-fonts as well.
Creating an SVG sprite
The approach we’ll be using in this article allows you to easily add any new SVGs to an icons
folder, run a script to generate your sprite, and create a reusable component to make these icons easy to use. This approach also allows you to version control your icons and modify or roll back as you need.
- Get a bunch of icons in an svg folder
Let’s create a src/icons
- or whatever fits your project structure - folder where we will place all our SVG icons.
- Create the SVG sprite
Next, to create the svg sprite, we need to install grunt
and grunt-svgstore
.
yarn add --dev grunt grunt-svgstore
Now, to create our grunt task, create a Gruntfile.js file in the root folder of your project and put the following contents in it
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
// PLUGINS CONFIG
svgstore: {
options: {
prefix: '', // This will prefix each <g> ID in the generated SVG. You do not need it, but you can add it to avoid conflicting icon names with any other SVG sprites
includedemo: true, // Do you want to generate a demo html file showing how the icons are used?
},
default: {
files: {
// destination_file (for the sprite): source_files (array of matchers pointing to your svg files)
'src/assets/svgs/icon-sprite.svg': ['src/icons/*.svg'],
},
},
},
});
grunt.loadNpmTasks('grunt-svgstore');
// Default task(s).
grunt.registerTask('default', ['svgstore']);
};
We have registered the svgstore
task as default, hence running grunt
should run the svgstore
task.
NOTE: The destination file is the generated SVG file path, we have set this to
src/assets/svgs/icon-sprite.svg
. Feel free to change this to any valid path in your code where you feel more comfortable have the sprite generated. The source_files regex matches all svg files in thesrc/icons
folder we created earlier
Check the grunt-svgstore documentation for more configuration options.
Finally, add the script to your package.json
...
"scripts": {
...
"generate-sprite": "grunt"
}
Now you can run yarn generate-sprite
to generate your svg sprite. A demo HTML file will also be generated alongside and you can open up the file to make sure that every icon looks right.
Inserting the SVG Sprite
To use our newly generated sprite, we need to insert it in the <body>
tag. We want to insert it as the uppermost item in the DOM.
One way to insert this would be to copy the entire contents of custom-icons.svg
and paste it in your index.html or layout component. But this wouldn’t exactly be ideal, now would it (especially if you’re using React or Vue or any other component-based framework that uses webpack to load files).
An alternative, more ideal approach would be to use webpack to load the SVG file as a component.
Using Vue: simply install and configure vue-svg-loader
. Then create the CustomSprite component.
<template>
<div style="display: none;">
<icon-sprite />
</div>
</template>
<script>
import IconSprite from 'assets/svgs/icon-sprite.svg';
export default {
components: {
IconSprite,
},
};
</script>
Using React: if your project is built with create-react-app and you have not yet ejected, then you don’t need to install anything, simply create the component below.
If your project has already been ejected, or you’re using NextJs
or something similar that allows you to configure webpack, you might want to take a look at @svgr/webpack
.
Create the CustomSprite component like such:
import React from 'react';
import { ReactComponent as IconSprite } from 'assets/svgs/icon-sprite.svg';
const CustomSprite = () => {
return (
<div style={{ display: 'none' }}>
<IconSprite />
</div>
);
};
export default CustomSprite;
Note that the component is wrapped in a hidden div (and it is styled inline). That’s because we don’t want to show the sprite on the page at all (the inline styling is to prevent a flash before css loads).
Now you can insert your CustomSprite component in the root of the page
<body>
// your custom icon sprite
<custom-sprite />
...
Using individual icons from the sprite.
Now we need to use our newly minted icon sprite to add any icons on the page.
<svg
class="icon"
aria-label="Rotate left"
>
<use xlink:href="#rotate-left" />
</svg>
We're almost there! But this seems like an awful lot of code to write just to use a rotate-left icon. So we're going to make it into a component instead.
Create a reusable SvgIcon component
Vue:
<template>
<svg
class="icon"
:aria-label="label"
>
<use :xlink:href="`#${icon}`" />
</svg>
</template>
<script>
export default {
props: {
icon: {
type: String,
required: true
},
label: {
type: String,
default: null
},
},
};
</script>
React
import React from 'react';
const SvgIcon = ({icon, label}) => {
return (
<svg
class="icon"
aria-label={label}
>
<use xlink:href={`#${icon}`} />
</svg>
);
};
export default SvgIcon;
That's it! You can now use your icons by using your component with the icon name anywhere.
<svg-icon icon="rotate-left" label="rotate-left icon" />
Or React
<SvgIcon icon="rotate-left" label="rotate-left icon" />
Feel free to modify the process to suit your needs. Now when you have new icons, all you have to do is
- Add them to your
src/icons
folder - Run
npm run custom-icons
oryarn custom-icons
and your new icons will be added to the sprite.
I hope this helps, if you run into any troubles, please feel free to let me know.
Top comments (5)
Good article and well explained the steps @itope84.
Am also written a post about svg icon generation and how to use in vue component. but, in different approach. In vue cli 3 in-build option have. For more info refer the below post:
dev.to/lakshmananarumugam/svg-icon...
👏🏽👏🏽👏🏽 Thanks for sharing.
So, I just want to confirm if the label or the icon prop being passed into the reusable component created in the latter part of the article has to match with the name of the svg file in the
src/icons
directory? Also, could the icons be discarded or they need to remain there even after generating the sprite?Thank you.
Yes, the name of the svg file will be passed as the
icon
prop. The label prop is used for accessibility (for screen readers).Theoretically, the icons could be discarded since the only thing we need is the svg sprite. But you don’t want to do that for several reasons
Version control. Ideally the sprite itself should not be committed to version control (to avoid merge conflicts). Hence the script generating the sprite (grunt) should be run as part of your CI/CD process, so you’ll want the icons to be there when that is run
You’ll need to add new icons someday (the point is to be able to automate it). If you’d deleted previous icons and then added new icons, the new sprite generated will only contain the new icons and you would’ve lost your previous icons
Makes sense. Thanks for the clarification.👍🏽
Very well structured. This blog post was very helpful. Keep up the good work.