The three main pillars of any design system are colors, type and icons. Having a system in place to create once and use anywhere is easy enough to do for colors and type with tokens, but what about icons? With a need to support React, iOS, Android and now React Native, we had to find a solution that was easy to maintain, but still robust enough to support all existing and future platforms.


Identifying the Problems, Defining the Goals

Before getting into the how, it’s important to understand the why. Maintaining our iconset across platforms was becoming more and more difficult with each addition, removal or change. It was difficult to keep it in sync across platforms, which was exacerbated by the process being almost entirely manual. With these types of things, the more human intervention there is, the more likely it is to have errors. And as the errors piled up, questions like these became common:

“Did you remember to export these out to React Native?”
“Did you get this group exported @3x?”

Nope, completely forgot on both counts! Additionally, if there was a change to how a particular platform was using the icons (we need @1.5x and also in PNG now), the library became even more out of sync. A recent example is us switching our react-native component library from flow to typescript.

So, when we set out to solve these problems, the goals made themselves pretty clear:

  1. The process needs to start in a Sketch file, maintainable by any one of our designers.
  2. The output process needs to be easy to maintain so adding, removing or changing platform requirements doesn’t become a burden.
  3. Must be able to support all of the platforms. If it can’t work for one platform, it might as well not work for any of them.

Start in Sketch

Our icons are designed in Sketch, so it only makes sense that our process starts there. Where before we might have multiple Sketch files handling our iconset, we now just have one split up into categories. Each category gets its own artboard and can hold as many icons as necessary. Some categories we have, for example, are:

Within the artboard, we have the icons (crazy, I know) and a title that sits above each icon. The title helps to make the icons easier to find since no one wants to dig through the layer list to see what an icon is called.

Now we get to the meat and potatoes of our Sketch setup. All of our icons sit in a 32x32 bounding box within a slice. We can size and shift as necessary within that box to ensure they all have the same visual weight. Since it’s a slice, the name of the slice determines how the icon will be exported. We name ours {Category}/{Name of Icon} so that at export, our icons will be placed in their respective category folder.

That’s the extent of our Sketch setup — pretty barebones, but that’s exactly what we wanted so that anyone could hop in and understand how to add or update an icon.

Generating Visual Assets

Now it’s time to jump into the code to actually start generating assets the platforms can consume. It’s important for us to do a little tablesetting before that happens to make things a bit easier to follow. To begin, our tokens and icons live in an /assets repo in GitHub. This repo contains the Sketch file above, our token files, and any scripting we’ve set up to produce assets for our various platforms.

When a user makes a change to any of those files, they only need to run the respective script (ex: a change to the icon file means they just need to run npm run generate-icons). These top-level scripts generate the visual assets and then generate the platform-specific code assets all in one go.

With that out of the way, let’s look at how we’re generating those visual assets (.pngs, .svgs, etc.). Within our scripts/icons folder, we have an assets/ folder. Within that assets/ folder we have a separate file for each type of visual asset we want to generate:

Those files contain very simple gulp scripts that export the visual assets we need. We’re using the gulp-sketch library because it makes exporting incredibly simple. Here in our generate-png file, you can see we’re telling it to export all of the slices (hey, it’s all coming together) into png format at 1x, 2x, and 3x sizes. The last bit there is telling it to put the icons into an output/icons/png folder which will be nicely categorized thanks to our slice naming convention.

gulp
.src('./icons/Uniform Icons.sketch')
.pipe(
sketch({
export: 'slices',
formats: 'png',
scales: '1.0,2.0,3.0',
})
)
.pipe(gulp.dest('./scripts/output/icons/png')
);

What’s great about this setup is you can do things like clean up your SVG en masse. Here’s a small taste of the kind of clean up we do with our SVGs:

.pipe(
svgmin({
plugins: [
{
removeTitle: true,
},
],
js2svg: {
pretty: true,
indent: 2,
},
})
)

After you’ve created files for all of the visual assets you want to export, you can bundle them all up into a single script for later — we simply created a generate-assets script that runs all of the generate-* functions.

Generating Code Assets

All of that would be a neat trick on its own, but generating the code assets for each platform is really where the fun begins. There’s not enough room to go over each individual platform, but because the process is similar for all of them, the following info should give you an idea of how it all works.

Within our scripts/icons folder, we have a platforms/ folder. Within that platforms/ folder we have a separate file for each platform:

Diving into the generate-react file specifically, let’s look at how we can use some simple scripting to quickly generate not only the .jsx files we need, but the export files as well. Near the top, you’ll notice we generate the individual React component files, a category index export file, and finally an overall index file that exports those category index exports.

function generateReactIcons() {
generateIconFiles();
generateIconCategoryIndexes();
generateIconIndex();
}

To start, let’s look at the generateIconFiles bit. First, we check out the icon directory to get a list of categories and begin by looping through those. Next we get a list of icons within each category and loop through those as well.

With each icon, we can generate a React component. You’ll see here we have a createJsxName function that allows us to do basic formatting things like remove spaces and ampersands, but generating the actual .jsx file contents is done through simple JavaScript templating. We can use the filename and path data grabbed from the icon files to generate the components. It’s not necessary to go line-by-line to talk about what we’re doing, what matters is how easily you can see the file you’re generating and how a little bit of templating can go a long way.

At this point, it’s just a matter of creating the folder and file and actually saving the files to the output folder. At the bottom of the file, we export that main function as well as the function that names the component. That way we can use the function in our generate-docs script to correctly display the name of the component on our site depending on the platform selected.

One Last Thing

You’re generating visual assets. You’re generating code assets. The last thing to do is to combine those all into one little bit so you just need to run one command. In our package.json file, you can see we’re running both the assets and platform scripts. It’s easy for just about anyone to run this script once all of your icon work is in the Sketch file.

And if you’re wondering, we do not commit our output/ folder to our assets/ repo because it’s not the final resting place for those files (the platform repos are) and it would get a bit gnarly with the amount of visual asset files.


Next Steps

This process has been iterated on for the better part of a year and has held up against everything from switching from Objective-C to Swift to changing our react-native library to TypeScript from flow. Changing one template file is much simpler than changing each individual file, and the number of errors we’ve experienced is significantly lower if not non-existent.

But that doesn’t mean we’ve stopped iterating. Here are a few things we’re looking to improve in the future:

  1. Look into using something other than gulp. While it made setting up our scripts much easier, it’s currently the only part of our process still using gulp. This is honestly not a high priority since it’s working just fine currently.
  2. Rather than export out of a separate Sketch file, look into exporting out of our Sketch library. Right now, we’re copy/pasting from our icon file into Sketch library which is not ideal (again, manual process = higher probability for mistakes). This is necessary in order to apply the different color swatches we want to the icons. Read more about this in our Building Our Sketch Library post.
  3. Simplify the slice creation process (generating the bounding box and name) with a Sketch plugin.
  4. Auto-generate PR’s for each platform. We’ve tried this before and have had mixed results. Opening a PR for each of our platforms remains a manual process for now, which isn’t difficult, just time-consuming.

There’s always more that can be done, but this definitely feels like a good start — we can generate everything from visual assets to test files to export references quickly and all of our icons are categorized with the correct icon name across platforms in our documentation. Stay tuned as we dig in next time on how we’re generating our color tokens using a very similar process.