How to Clean SVGs in bulk

Oftentimes, we need to use specific icon sets from a design for a site rebuild. SVGs are often the best way to display these on the frontend because they’re more performant, more flexible and much easier to work with than plain images.

However, the icons that get exported from Zeplin/Sketch/whatever have a lot of junk code in them and end up quite difficult to style because of inconsistencies.

I’ve developed a workflow for cleaning SVG icons and pushing them out in a consistent size and format and have used it on several sites after some trial and error. The process is pretty manual, but the output is significantly higher quality and the icons will be much easier to work with. If you follow this process, you can do this once for almost all the icons on the site and forget about it from then on.

I’ve forgotten how to do this before, so I figured I would finally document cleaning SVGs up while doing a recent icon set before I forgot again 😉

For these steps I’ve used Adobe Illustrator, although some other vector-editing program may also work.

Export from Sketch or whatever source file

This step depends on whatever source file they’re coming from, typically they come from Sketch and that involves right-clicking on an icon and copying out the SVG code.

Create a new file in Illustrator with artboards:

{n} artboards, 20px*20px size each

In Illustrator, you can create a single document with multiple artboards – we want individual artboards for each icon so that they are all consistent sizes when we export. Create more artboards than you initially need – designers always, always forget to put some icons into the design library and you’ll need to add more icons later on in the project.

The 20x20px was more or less randomly chosen, but I’ve found it to be a manageable size when viewing all of the icons in Illustrator. you will control the output size in your CSS and the most important piece here is to use the same size for all the artboards for consistency.

Here is the setup I used for a recent project:

Example of setting up artboards for multiple AVGs in Illustrator

Pull out the SVG code and paste into Illustrator

Next, you will pull each icon into an artboard, naming the artboard according to the icon you put in there.

Couple of items to remember:

Perfectly organized artboards in Illustrator

Remove extra groups from icons

Pretty much every single icon you import will have a whole load of extra, unnecessary garbage that should be removed. Remove all of the extra groupings.

You will likely start out with something like this in the “Layers” tab:

Dirty SVG with a bunch of unnecessary groups

It should look like this when you’re done:

Cleaned SVGs

Export from Illustrator into a directory (as SVG and per-artboard)

Each icon should be exported into its own unique file.

Click Export As:

How to save SVGs out from artboards in Adobe Illustrator

Export the icons as SVG, and click “Use artboards”. This will export each icon individually.

Caption settings for saving SVG artboards in Adobe Illustrator

A new dialog will popup, I rarely change these settings but feel free to take a look through them

Adobe Illustrator SVG save options

After exporting, you’ll end up with a folder with all of your icons exported into individual files.

A list of cleaned SVG files

Run each SVG through SVGOMG

Now that you’ve exported the icons, you’ll have consistently sized and colored icons, but they’ll still have quite a bit of extra junk in them. There is a free tool called SVGOMG that will clean up the rest of the extra junk from the icon files.

https://jakearchibald.github.io/svgomg/

An alternative to uploading them one by one and using the GUI is to go straight to the svgo CLI tool which you can find at https://github.com/svg/svgo. This is the underlying tech behind the SVGOMG tool, but would be must faster iterating over files via Terminal.

The difference here can be quite dramatic. As an example, I picked out a random icon from the a recent set and pulled out the before and after code:

Before:

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
    <!ENTITY st0 "filter:url(#Adobe_OpacityMaskFilter);">
    <!ENTITY st1 "fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;">
    <!ENTITY st2 "mask:url(#mask-2_1_);fill-rule:evenodd;clip-rule:evenodd;fill:#1C1B1A;">
    <!ENTITY st3 "fill:url(#XMLID_7_);">
    <!ENTITY st4 "fill:#FFFFFF;">
    <!ENTITY st5 "fill:url(#XMLID_9_);">
    <!ENTITY st6 "fill:url(#XMLID_14_);">
    <!ENTITY st7 "fill:url(#XMLID_15_);">
    <!ENTITY st8 "fill:#C58930;">
    <!ENTITY st9 "fill:url(#XMLID_16_);">
    <!ENTITY st10 "fill:url(#XMLID_18_);">
    <!ENTITY st11 "fill:#F58357;">
    <!ENTITY st12 "fill:url(#XMLID_20_);">
    <!ENTITY st13 "fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st14 "fill-rule:evenodd;clip-rule:evenodd;fill:#FEFEFE;">
    <!ENTITY st15 "filter:url(#Adobe_OpacityMaskFilter_1_);">
    <!ENTITY st16 "mask:url(#mask-2_2_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st17 "filter:url(#Adobe_OpacityMaskFilter_2_);">
    <!ENTITY st18 "mask:url(#mask-2_3_);fill-rule:evenodd;clip-rule:evenodd;fill:#050000;">
    <!ENTITY st19 "filter:url(#Adobe_OpacityMaskFilter_3_);">
    <!ENTITY st20 "mask:url(#mask-2_4_);fill-rule:evenodd;clip-rule:evenodd;fill:#050000;">
    <!ENTITY st21 "fill-rule:evenodd;clip-rule:evenodd;fill:#050000;">
    <!ENTITY st22 "filter:url(#Adobe_OpacityMaskFilter_4_);">
    <!ENTITY st23 "mask:url(#mask-2_5_);fill-rule:evenodd;clip-rule:evenodd;fill:#050000;">
    <!ENTITY st24 "filter:url(#Adobe_OpacityMaskFilter_5_);">
    <!ENTITY st25 "mask:url(#mask-2_6_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st26 "filter:url(#Adobe_OpacityMaskFilter_6_);">
    <!ENTITY st27 "mask:url(#mask-2_7_);fill-rule:evenodd;clip-rule:evenodd;fill:#050000;">
    <!ENTITY st28 "filter:url(#Adobe_OpacityMaskFilter_7_);">
    <!ENTITY st29 "mask:url(#mask-2_8_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st30 "filter:url(#Adobe_OpacityMaskFilter_8_);">
    <!ENTITY st31 "mask:url(#mask-2_9_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st32 "filter:url(#Adobe_OpacityMaskFilter_9_);">
    <!ENTITY st33 "mask:url(#mask-2_10_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st34 "filter:url(#Adobe_OpacityMaskFilter_10_);">
    <!ENTITY st35 "mask:url(#mask-2_11_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st36 "filter:url(#Adobe_OpacityMaskFilter_11_);">
    <!ENTITY st37 "mask:url(#mask-2_12_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st38 "filter:url(#Adobe_OpacityMaskFilter_12_);">
    <!ENTITY st39 "mask:url(#mask-2_13_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st40 "filter:url(#Adobe_OpacityMaskFilter_13_);">
    <!ENTITY st41 "mask:url(#mask-2_14_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st42 "filter:url(#Adobe_OpacityMaskFilter_14_);">
    <!ENTITY st43 "mask:url(#mask-2_15_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st44 "filter:url(#Adobe_OpacityMaskFilter_15_);">
    <!ENTITY st45 "mask:url(#mask-2_16_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st46 "filter:url(#Adobe_OpacityMaskFilter_16_);">
    <!ENTITY st47 "mask:url(#mask-2_17_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st48 "filter:url(#Adobe_OpacityMaskFilter_17_);">
    <!ENTITY st49 "mask:url(#mask-2_18_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st50 "fill-rule:evenodd;clip-rule:evenodd;fill:#C6C7C9;">
    <!ENTITY st51 "filter:url(#Adobe_OpacityMaskFilter_18_);">
    <!ENTITY st52 "mask:url(#mask-2_20_);fill-rule:evenodd;clip-rule:evenodd;fill:#C6C7C9;">
    <!ENTITY st53 "filter:url(#Adobe_OpacityMaskFilter_19_);">
    <!ENTITY st54 "mask:url(#mask-2_19_);fill-rule:evenodd;clip-rule:evenodd;fill:#C6C7C9;">
    <!ENTITY st55 "filter:url(#Adobe_OpacityMaskFilter_20_);">
    <!ENTITY st56 "mask:url(#mask-2_21_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st57 "filter:url(#Adobe_OpacityMaskFilter_21_);">
    <!ENTITY st58 "mask:url(#mask-2_22_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st59 "filter:url(#Adobe_OpacityMaskFilter_22_);">
    <!ENTITY st60 "mask:url(#mask-2_23_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st61 "filter:url(#Adobe_OpacityMaskFilter_23_);">
    <!ENTITY st62 "mask:url(#mask-2_24_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st63 "filter:url(#Adobe_OpacityMaskFilter_24_);">
    <!ENTITY st64 "mask:url(#mask-2_25_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st65 "filter:url(#Adobe_OpacityMaskFilter_25_);">
    <!ENTITY st66 "mask:url(#mask-2_26_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st67 "filter:url(#Adobe_OpacityMaskFilter_26_);">
    <!ENTITY st68 "mask:url(#mask-2_27_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st69 "filter:url(#Adobe_OpacityMaskFilter_27_);">
    <!ENTITY st70 "mask:url(#mask-2_28_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st71 "filter:url(#Adobe_OpacityMaskFilter_28_);">
    <!ENTITY st72 "mask:url(#mask-2_29_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st73 "filter:url(#Adobe_OpacityMaskFilter_29_);">
    <!ENTITY st74 "mask:url(#mask-2_30_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st75 "filter:url(#Adobe_OpacityMaskFilter_30_);">
    <!ENTITY st76 "mask:url(#mask-2_31_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st77 "filter:url(#Adobe_OpacityMaskFilter_31_);">
    <!ENTITY st78 "mask:url(#mask-2_32_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st79 "filter:url(#Adobe_OpacityMaskFilter_32_);">
    <!ENTITY st80 "mask:url(#mask-2_33_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st81 "filter:url(#Adobe_OpacityMaskFilter_33_);">
    <!ENTITY st82 "mask:url(#mask-2_34_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st83 "filter:url(#Adobe_OpacityMaskFilter_34_);">
    <!ENTITY st84 "mask:url(#mask-2_35_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st85 "filter:url(#Adobe_OpacityMaskFilter_35_);">
    <!ENTITY st86 "mask:url(#mask-2_36_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st87 "filter:url(#Adobe_OpacityMaskFilter_36_);">
    <!ENTITY st88 "mask:url(#mask-2_37_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st89 "filter:url(#Adobe_OpacityMaskFilter_37_);">
    <!ENTITY st90 "mask:url(#mask-2_38_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st91 "filter:url(#Adobe_OpacityMaskFilter_38_);">
    <!ENTITY st92 "mask:url(#mask-2_39_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st93 "filter:url(#Adobe_OpacityMaskFilter_39_);">
    <!ENTITY st94 "mask:url(#mask-2_40_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st95 "filter:url(#Adobe_OpacityMaskFilter_40_);">
    <!ENTITY st96 "mask:url(#mask-2_41_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st97 "filter:url(#Adobe_OpacityMaskFilter_41_);">
    <!ENTITY st98 "mask:url(#mask-2_42_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st99 "filter:url(#Adobe_OpacityMaskFilter_42_);">
    <!ENTITY st100 "mask:url(#mask-2_43_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st101 "filter:url(#Adobe_OpacityMaskFilter_43_);">
    <!ENTITY st102 "mask:url(#mask-2_44_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st103 "filter:url(#Adobe_OpacityMaskFilter_44_);">
    <!ENTITY st104 "mask:url(#mask-2_45_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st105 "filter:url(#Adobe_OpacityMaskFilter_45_);">
    <!ENTITY st106 "mask:url(#mask-2_46_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st107 "filter:url(#Adobe_OpacityMaskFilter_46_);">
    <!ENTITY st108 "mask:url(#mask-2_47_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st109 "filter:url(#Adobe_OpacityMaskFilter_47_);">
    <!ENTITY st110 "mask:url(#mask-2_48_);fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;">
    <!ENTITY st111 "filter:url(#Adobe_OpacityMaskFilter_48_);">
    <!ENTITY st112 "mask:url(#mask-2_49_);fill-rule:evenodd;clip-rule:evenodd;">
    <!ENTITY st113 "fill:#696969;">
    <!ENTITY st114 "fill:url(#XMLID_21_);">
    <!ENTITY st115 "fill:url(#XMLID_23_);">
    <!ENTITY st116 "fill:url(#XMLID_24_);">
    <!ENTITY st117 "fill-rule:evenodd;clip-rule:evenodd;">
]>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
<defs>
    <filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="0" y="4" width="20" height="12.13">
        <feColorMatrix  type="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0"/>
    </filter>
</defs>
<mask maskUnits="userSpaceOnUse" x="0" y="4" width="20" height="12.13" id="mask-2_36_">
    <g style="&st0;">
        <polygon id="path-1_36_" style="&st1;" points="20,4 20,16.13 0,16.13 0,4         "/>
    </g>
</mask>
<path id="Fill-1_43_" style="&st86;" d="M18.88,13.84l-1.6-0.8c-0.3-0.15-0.67-0.03-0.82,0.27c-0.15,0.3-0.03,0.66,0.27,0.82
    l1.6,0.8c0.09,0.04,0.18,0.07,0.27,0.07c0.22,0,0.44-0.12,0.54-0.34C19.31,14.36,19.18,13.99,18.88,13.84 M17.01,6.22
    c0.09,0,0.18-0.02,0.27-0.06l1.6-0.8c0.3-0.15,0.42-0.52,0.27-0.82c-0.15-0.3-0.51-0.42-0.82-0.27l-1.6,0.8
    c-0.3,0.15-0.42,0.51-0.27,0.81C16.58,6.1,16.79,6.22,17.01,6.22 M19.39,8.99h-1.79C17.27,8.99,17,9.26,17,9.6
    c0,0.34,0.27,0.61,0.61,0.61h1.79c0.34,0,0.61-0.27,0.61-0.61C20,9.26,19.73,8.99,19.39,8.99 M14.02,4.81v9.62
    c0,0.27-0.13,0.52-0.35,0.67c-0.14,0.1-0.3,0.14-0.46,0.14c-0.1,0-0.19-0.02-0.29-0.05c-3.29-1.25-5.82-2.15-7.69-2.79l0.29,2.7
    c0.02,0.07,0.03,0.15,0.03,0.23c0,0.45-0.36,0.81-0.81,0.81H4.73H3.37c-0.41,0-0.76-0.31-0.81-0.72l-0.39-3.62H0.81
    C0.36,11.79,0,11.43,0,10.98V8.22c0-0.45,0.36-0.81,0.81-0.81h2.75c1.92-0.63,4.96-1.68,9.36-3.36c0.25-0.1,0.53-0.06,0.75,0.09
    C13.89,4.3,14.02,4.55,14.02,4.81"/>
</svg>

After:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" enable-background="new 0 0 20 20">
    <defs><filter id="a" filterUnits="userSpaceOnUse" x="0" y="4" width="20" height="12.13"><feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/></filter></defs>
    <mask maskUnits="userSpaceOnUse" x="0" y="4" width="20" height="12.13" id="b"><path fill-rule="evenodd" clip-rule="evenodd" fill="#fff" d="M20 4v12.13H0V4z" filter="url(#a)"/></mask>
    <path d="M18.88 13.84l-1.6-.8a.613.613 0 0 0-.82.27c-.15.3-.03.66.27.82l1.6.8c.09.04.18.07.27.07.22 0 .44-.12.54-.34.17-.3.04-.67-.26-.82m-1.87-7.62c.09 0 .18-.02.27-.06l1.6-.8a.61.61 0 1 0-.55-1.09l-1.6.8c-.3.15-.42.51-.27.81.12.22.33.34.55.34m2.38 2.77H17.6c-.33 0-.6.27-.6.61 0 .34.27.61.61.61h1.79c.34 0 .61-.27.61-.61a.621.621 0 0 0-.62-.61m-5.37-4.18v9.62a.807.807 0 0 1-.81.81c-.1 0-.19-.02-.29-.05a245.29 245.29 0 0 0-7.69-2.79l.29 2.7c.02.07.03.15.03.23 0 .45-.36.81-.81.81H3.37a.82.82 0 0 1-.81-.72l-.39-3.62H.81a.822.822 0 0 1-.81-.82V8.22c0-.45.36-.81.81-.81h2.75c1.92-.63 4.96-1.68 9.36-3.36.25-.1.53-.06.75.09.22.16.35.41.35.67" mask="url(#b)" fill-rule="evenodd" clip-rule="evenodd" fill="#231f20"/>
</svg>

I eventually also manually deleted the mask and fill values you see above, and this is what the browser actually sees when we get to the end:

<svg class="icon icon--announcement icon--blue" aria-hidden="true" viewBox="0 0 20 20" version="1.1">
    <title>announcement</title>
    <path d="M18.88 13.84l-1.6-.8a.613.613 0 0 0-.82.27c-.15.3-.03.66.27.82l1.6.8c.09.04.18.07.27.07.22 0 .44-.12.54-.34.17-.3.04-.67-.26-.82m-1.87-7.62c.09 0 .18-.02.27-.06l1.6-.8a.61.61 0 1 0-.55-1.09l-1.6.8c-.3.15-.42.51-.27.81.12.22.33.34.55.34m2.38 2.77H17.6c-.33 0-.6.27-.6.61 0 .34.27.61.61.61h1.79c.34 0 .61-.27.61-.61a.621.621 0 0 0-.62-.61m-5.37-4.18v9.62a.807.807 0 0 1-.81.81c-.1 0-.19-.02-.29-.05a245.29 245.29 0 0 0-7.69-2.79l.29 2.7c.02.07.03.15.03.23 0 .45-.36.81-.81.81H3.37a.82.82 0 0 1-.81-.72l-.39-3.62H.81a.822.822 0 0 1-.81-.82V8.22c0-.45.36-.81.81-.81h2.75c1.92-.63 4.96-1.68 9.36-3.36.25-.1.53-.06.75.09.22.16.35.41.35.67" fill-rule="evenodd" class="icon-primary-color"></path> 
</svg>

You’ll need to drag and drop each file individually into the tool and then download it. I then keep them in a new “Cleaned” folder.

Build an SVG output entry point in your code

The final step is to bundle your SVGs into a usable entry point in your code. This can take a lot of various shapes, but I have a couple of tips for this:

  1. When adding an icon into your code, remove all unnecessary IDs, styles, and other code; leaving only the paths/rects/etc. The exception is if you have complex colored SVGs.
  2. Assign each colored element (path, rect, etc.) a common class, such as .icon-main-color or .secondary-color.
  3. Allow your entrypoint to define a color and/or size class that controls the overall SVG.

Upload the original .ai file and all of the exported icons into a shared folder

You’ll likely want these icons available to anyone else on the project, so be sure to finish up by uploading all of your work up to a shared folder so that others can reference or edit the icons. Icons are typically added throughout the life of a project so the .ai file will serve as a living reference.

Hopefully this will help you with your process for cleaning SVGs!