Automating SVG Icon Generation
I’m quite proud of this week’s achievement: I automated the process of updating icons in a React app from a Figma file. I’m sharing the details for my future self and whoever might find it useful.
The Problem
The app I’m working on has a lot of icons in SVG format. The icons are scattered all over the codebase and are not consistent, due to the way we work: multiple independent teams, working on a very lean way, without a dedicated design system team.
Maintaining the icons is a pain. Every time we need to update an icon, we have to:
- Open the Figma file
- Export the icon as SVG
- Tweak the SVG code to make it work in the app (e.g., make sure the
fill
attribute is set tocurrentColor
, etc.) - Replace the old SVG with the new one in the codebase
This process is error-prone, time-consuming, and needs to be done by a developer. Designers don’t own the process, and they can’t update the icons themselves.
Until now.
The Solution
Step 1: Unify the Icons
I started by creating a single Icon
component in React, to be used throughout the app. This component has a name
prop to pick the icon to display. Thanks to TypeScript types, the name
is autocompleted in most code editors, reducing errors while consuming the icons. It also has a size
prop to choose from a predefined list of sizes and a color
prop to set the color, with sane defaults.
The way the Icon
component works is simple: it imports all the icons from an index file and then picks the right one based on the name
prop. This way, we can easily add new icons by adding them to the file, without having to change the Icon
component.
import * as iconIndex from "../../icons";
type IconProps = {
name: keyof typeof icons;
size?: 14 | 32 | 64;
color?: string;
};
export const Icon = ({ name, size = 14, color = "white" }: IconProps) => {
const SvgIcon = iconIndex[name];
return (
<SvgIcon style={{ color: color }} width={size} height={size} aria-hidden />
);
};
Step 2: Automate the Icon index
The icons
folder contains all the icons in SVG format. We use the SVGR package to convert the SVG files to React components (in particular, since we use Vite, we use the vite-plugin-svgr package, more on this later).
The file with all the icons is automatically generated by a script that imports whatever is in the icons
folder, and exports them as named exports. This way, we can add new icons to the app by simply adding them to the icons
folder. The script can be run manually by developers whenever they need to update the icons, but is also run automatically in the CI/CD pipeline as part of the build process.
import fs from "fs";
import path from "path";
console.log("Updating icons...");
const iconsDir = path.resolve("./src/icons/");
const cleanUpIconName = (name) => {
return name.replace(".svg", "").replace(/[-.]/g, "_").replace("icon_", "");
};
const svgFiles = fs
.readdirSync(iconsDir)
.filter((file) => file.endsWith(".svg"))
.sort((a, b) => cleanUpIconName(a).localeCompare(cleanUpIconName(b)));
const imports = svgFiles
.map((file) => {
const variableName = cleanUpIconName(file);
return `import ${variableName} from "./${file}?react";`;
})
.join("\n");
const exports = svgFiles
.map((file) => {
const variableName = cleanUpIconName(file);
return `export { ${variableName} };`;
})
.join("\n");
const content = `${imports}\n\n${exports}\n`;
fs.writeFileSync(path.join(iconsDir, "index.ts"), content);
console.log("Finished updating icons.", svgFiles.length, "icons updated.");
Step 3: Clean up the SVGs with SVGR
The SVGs are not always perfect. Sometimes they have extra attributes, or they are not optimized for the web. We use the svgo
plugin to optimize the SVGs before converting them to React components. This is done by the vite-plugin-svgr
package, which is a wrapper around SVGR for Vite.
Our vite.config.js
file looks like this (simplified):
import svgr from "vite-plugin-svgr";
export default {
plugins: [
svgr({
svgrOptions: {
svgo: true,
replaceAttrValues: {
white: "currentColor"
}
}
})
]
};
The key part here is the replaceAttrValues
option, which replaces the white
color with currentColor
. This is important because we want to be able to change the color of the icons using CSS, without having to modify the SVG code itself manually. You can pick whatever color makes sense for you, or even use multiple colors. Our icons are all monochrome, so we only need to replace one color, and white is a good default. But you can experiment with picking an uncommon color, like pink, to make sure you don’t accidentally use it in the SVGs.
This is how the icons look in the Figma file:
And this is how they look in the app, once converted to React components:
We use Storybook to showcase the icons, so that developers can easily find the one they need. The icons are displayed in two tones to make sure we’re coloring the right parts of the icon. They also zoom on hover, to make it easier to see the details.
A nice side effect of this process is that it is now easier to detect inconsistencies in the icons. For example, if two icons are supposed to represent the same thing but look different, it’s easier to spot that now that they are all in one place. Or if two icons look the same but have different names, it’s easier to spot that too:
Step 4: Automate the export process
This is the most important part, and the one that makes the whole process worth it: we’re using the Figma API to download the icons directly from the Figma file. This way, designers can update the icons in Figma, and the changes will automatically propagate to the app.
To do that, we use the Figma API and the figma-export-icons package, plus a couple of custom scripts to make it work with our setup. Basically, deleting the old icons before exporting the new ones, renaming icons once they’re downloaded, etc. Pretty straightforward stuff.
This step is not run automatically in the CI/CD pipeline, because we still want to have some control over when the icons are updated. But it could be done, if we wanted to.
Future Improvements
This is just the beginning. Here are some ideas for future improvements:
- Use a custom script to download the icons from Figma, instead of relying on a third-party package. I’m not entirely happy with the way the
figma-export-icons
package works, and I think I can do better. - Add a search functionality to the Storybook catalog, so that developers can easily find the icon they need. This would require some more work, but it’s definitely doable and would save a lot of time.
- Add a “copy to clipboard” functionality to the Storybook catalog, so that developers can easily copy the icon name to the clipboard. This would close the loop and make it even easier to use the icons in the app.
- Use Figma webhooks to automatically update the icons in the app whenever they change in Figma. This would require some more work, but it’s definitely doable and would save a lot of time.
Conclusion
This was a lot of work, but I really enjoyed the process and the end result.
I’m happy that I was able to automate the process of updating icons in the app, and I’m looking forward to seeing how it will improve the workflow of the team.