DEV Community

Ilesanmi Temitope
Ilesanmi Temitope

Posted on

The hitchhiker's guide to reusable Icons and SVG sprite generation

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.

  1. 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.
Folder structure

  1. Create the SVG sprite

Next, to create the svg sprite, we need to install grunt and grunt-svgstore.

yarn add --dev grunt grunt-svgstore
Enter fullscreen mode Exit fullscreen mode

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']);
};
Enter fullscreen mode Exit fullscreen mode

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 the src/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"
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 />
  ...
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

React

import React from 'react';
const SvgIcon = ({icon, label}) => {
  return (
    <svg
      class="icon"
      aria-label={label}
    >
      <use xlink:href={`#${icon}`} />
    </svg>
  );
};

export default SvgIcon;
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

Or React

<SvgIcon icon="rotate-left" label="rotate-left icon" />
Enter fullscreen mode Exit fullscreen mode

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 or yarn 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)

Collapse
 
lakshmananarumugam profile image
Lakshmanan Arumugam • Edited

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...

Collapse
 
iamwebwiz profile image
Ezekiel Oladejo

👏🏽👏🏽👏🏽 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.

Collapse
 
itope84 profile image
Ilesanmi Temitope

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

  1. 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

  2. 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

Collapse
 
iamwebwiz profile image
Ezekiel Oladejo

Makes sense. Thanks for the clarification.👍🏽

Collapse
 
thearlsankhalid profile image
Arslan Khalid

Very well structured. This blog post was very helpful. Keep up the good work.