Learn how to deliver SVG icons to React using the Figma API

Viacheslav Borodulin
Level Up Coding
Published in
11 min readMar 25, 2020

--

Figma is an amazing design tool. The most interesting and exciting feature, that was introduced last year, is the Figma API. The main idea is that by using the REST API, developers can get a whole bunch of information from the design project.

There are many different ways to apply this information to your frontend project. How to access it and what to do with it we will reveal further.

How does the Figma API work?

Figma has great documentation. You can create an auth token and try the API on your Figma project right there. However, let’s dig a little deeper and straighten it out together.

Figma project example.

We have a Figma document opened in the browser. Let’s create a frame. Then draw a red square shape using shape tools. Then set its border-radius (corner radius in properties panel in Figma) to 6 px.

This file in Figma has a key. We can find it in the address bar in the browser.

Any element in Figma is a node. Every node has id property, children nodes, and other properties. In the browser, we can get node id only for a frame from the address bar by selecting any elements inside.

NOTE: node_id from the address bar is URI decoded value. To get the real value you need to decode it. You can do that with JavaScript method decodeURIComponent.

Make a simple API request to get our frame information:

GET https://api.figma.com/v1/files/B1v7c2kZ8EnvF3tLlxmT69/nodes?ids=2%3A2

NOTE: to make API requests you should get personal access token and use it by passing the token to the API in the header X-Figma-Tokenof your request.

We receive a response in JSON format:

{
"name": "how-to-delivery-svg-from-figma-to-react",
"nodes": {
"2:2": {
"document": {
"id": "2:2",
"name": Frame 1",
"type": "FRAME",
"blendMode": "PASS_THROUGH",
"children": [
{
"id": "2:4",
"name": "Rectangle 1",
"type": "RECTANGLE",
"blendMode": "PASS_THROUGH",
"absoluteBoundingBox": {
"x": 128.0,
"y": 87.0,
"width": 100.0,
"height": 100.0
},
"constraints": {...},
"fills": [
{
"blendMode": "NORMAL",
"type": "SOLID",
"color": {
"r": 1.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}

}
],
"strokes": [...],
"strokeWeight": 1.0,
"strokeAlign": "INSIDE",
"effects": [...],
"cornerRadius": 6.0,
"rectangleCornerRadii": [...]
}
],
"absoluteBoundingBox": {...},
"constraints": {...},
"clipsContent": true,
"background": [...],
"fills": [...],
"strokes": [...],
"strokeWeight": 1.0,
"strokeAlign": "INSIDE",
"backgroundColor": {...},
"effects": [...]
},
"components": {...},
"schemaVersion": 0,
"styles": {...}
}
}
}

If we look closely at the structure, we can find the node with id: “2:2”. This document property is a representation frame from Figma. Similar to the structure in the design project, the first child of the frame is a red square shape, that we drew earlier.

We can parsefills and cornerRadiusproperties and generate CSS:

.box {
border-radius: 6px;
background-color: rgba(255, 0, 0, 0);
}

It looks magical, isn’t it?🦄 Any element has its id, and we can take this element and get from the URL all the information about it and all its children.

GET https://api.figma.com/v1/files/:file_key/nodes?ids=:node_id

This gives you the ability to view and extract any objects or layers, and their properties.

So, using the Figma API we can view and extract any objects or layers, and their properties and use it for syncing Figma designs to React components.

However, things aren’t that simple. SVG icons and local document style are the easiest things that can be delivered in the frontend project. Design tokens are a little bit more complex. And the most difficult thing to deliver is molecules and organisms.

Why do we need to automate delivery SVG icons to the React project?

Using the standard approach asset delivery from the design system, a developer needs to perform many routine actions, such as to find the icon, to export it, to insert it in the project and therefore, a designer must constantly notify the developer of changes.

Automation saves time on the routine activities, simplifies the interaction between the design and development team. And most importantly, it just speeds up product delivery! 🚀

SVG requirements

For the most comfortable and flexible SVG icons use on the frontend, each icon must satisfy the following:

  • Use a square viewBox attribute, preferably 0 0 24 24
  • Use only a single color (e.g. black)
  • For best results, use only<path> elements
  • Do not use transforms

All this will allow you to easily scale the size of the icons and change their color with CSS.

Figma requirements

Requirements for using SVG icons in Figma project are the following:

  • Icons should be made as components and be placed in one frame.
  • The name of the icons in Figma will determine the icon’s name in the frontend project, so the designer and developer should interact more closely. Recommend param-case naming style with mask icon-[name]. For example: icon-like.

React project requirements

The only requirement is the ability to import SVG directly into React as a component.

import { ReactComponent as Logo } from './logo.svg';const App = () => {
return (
<div>
{/* Logo is an actual React component */}
<Logo />
</div>
);
}

If you use create-react-app, you need react-scripts@2.0.0 and higher, and react@16.3.0 and higher.

If you use custom webpack configuration, you need to add a rule for the .svg extension.

{
test: /\.svg$/,
use: ['@svgr/webpack', 'url-loader'],
}

Project structure

In the project root dir, create a figma-import folder with separate dependencies, where we implement the main functionality for importing icons.

Icons will be placed in src/components/Icons folder.

The main Figma settings are specified in a .env file. To load environment variables from a .env file into process.env we use dotenv library.

.envFIGMA_TOKEN="31972-e6f223ff-3ca1-4c35-ba90-548da496b4vb"
FILE_KEY="B1v7c2kZ8EkvF3tLlxmT69"
FRAME_WITH_ICONS_ID="2:3"

NOTE: frame id from the browser address bar is URL encoded value. In order to get value FRAME_WITH_ICONS_ID for .env file, we must decode it.

Getting icons content

As mentioned earlier in Figma requirements, all icons must be placed in a specific frame.

Make API request to known URL address:

GET https://api.figma.com/v1/files/B1v7c2kZ8EnvF3tLlxmT69/nodes?ids=2%3A3

We receive a response:

{
"name": "how-to-delivery-svg-from-figma-to-react",
"nodes": {
"2:3": {
"document": {
"id": "2:3",
"name": "frame-with-icons",

"type": "FRAME",
"blendMode": "PASS_THROUGH",
"children": [
{
"id": "19:27",
"name": "chicken",

...
},
{
"id": "19:28",
"name": "prawn",

...
},
{
"id": "19:29",
"name": "pizza",

...
},
{
"id": "19:30",
"name": "muffin",

...
},
{
"id": "19:31",
"name": "lollipop",

...
}
],
},
"components": {...}
}
}

Then, we find out our frame and see icons in the children array.

Further, we should loop throw children array, save icon name (this necessary for importing icon as a component to React project) and make a new request for each icon.

GET https://api.figma.com/v1/images/B1v7c2kZ8EnvF3tLlxmT69/?ids=:icon_node_id&format=svg

For icon with name=”lollipop” and id=”19:31” using encodeURIComponent for id, make a request:

GET https://api.figma.com/zwv1/images/B1v7c2kZ8EnvF3tLlxmT69/?ids=19%3A31&format=svg

We receive a response:

{
"err": null,
"images": {
"19:31": "https://s3-us-west-2.amazonaws.com/figma-alpha-api/img/d330/8ced/303c7231dbcd55d5cc6a8f7200f6d71f"
}
}

And, according to the received URL, we can download the file:

GET https://s3-us-west-2.amazonaws.com/figma-alpha-api/img/d330/8ced/303c7231dbcd55d5cc6a8f7200f6d71f

In the response we get the content of the icon itself:

<svg width="54" height="80" viewBox="0 0 54 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M53.602 26.801C53.602 12.023 41.579 0 26.801 0C12.023 0 0 12.023 0 26.801C0 40.8971 10.9413 52.4776 24.7762 53.5182V80H29.6125V53.4537C43.0726 52.0431 53.602 40.6295 53.602 26.801ZM26.8002 48.7657C16.5441 48.7657 7.91053 41.6991 5.50448 32.1806C5.54962 21.3344 13.7447 13.1659 24.6053 13.1659C32.9971 13.1668 39.8251 19.9931 39.8251 28.3841C39.8251 34.83 34.5793 40.075 28.1326 40.0758C23.2423 40.0742 19.2645 36.0955 19.2645 31.2052C19.2645 27.5603 22.2307 24.5941 25.8757 24.5941C28.5251 24.5941 30.6813 26.751 30.6813 29.4013C30.6813 30.2976 30.3315 31.1408 29.6971 31.7759C29.0612 32.4111 28.2172 32.7609 27.3185 32.7617C25.9829 32.7625 24.9003 33.8458 24.9012 35.1807C24.902 36.5155 25.9845 37.598 27.3193 37.598C27.3193 37.598 27.3193 37.598 27.3209 37.598C29.5093 37.5972 31.5672 36.7436 33.1156 35.1968C34.6648 33.6484 35.5168 31.5889 35.5168 29.4013C35.5168 24.0854 31.1923 19.7602 25.8757 19.7586C19.5627 19.7586 14.4274 24.8939 14.4274 31.206C14.4274 38.7619 20.5751 44.9112 28.1318 44.9128C37.2457 44.9112 44.6614 37.4972 44.6614 28.3841C44.6614 17.3268 35.6643 8.33048 24.6061 8.32967C18.5648 8.32967 13.1168 10.4649 8.9463 14.0316C12.9354 8.46992 19.4491 4.83627 26.8002 4.83627C38.9118 4.83627 48.7649 14.6894 48.7649 26.801C48.7657 38.9118 38.9118 48.7657 26.8002 48.7657Z" fill="black"/>
</svg>

Thus, we got the names and content of all the icons that are in the Figma project.

To work with the Figma API and to get icon content, you can create a set of methods in the api.js file. For easy HTTP requests making, we will use the axios library.

// figma-imports/utils/api.jsconst api = require('axios');const headers = {
'X-FIGMA-TOKEN': process.env.FIGMA_TOKEN,
};
/**
* api endpoint for files
*
*/
const instanceFiles = api.create({
baseURL: `https://api.figma.com/v1/files/${process.env.FILE_KEY}`,
headers,
});
/**
* api endpoint for images
*
*/
const instanceImages = api.create({
baseURL: `https://api.figma.com/v1/images/${process.env.FILE_KEY}`,
headers,
});
/**
* get Figma document info
*
* @return {Promise<Object>}
*/
const getDocument = async () => instanceFiles.get('/');
/**
* get Figma node info
*
* @param {string} nodeId
* @return {Promise<Object>}
*/
const getNode = async (nodeId) => instanceFiles.get(`/nodes?ids=${decodeURIComponent(nodeId)}`);
/**
* get Figma node children
*
* @param {string} nodeId
* @return {Promise<[Object]>}
*/
const getNodeChildren = async (nodeId) => {
const {data: {nodes}} = await instanceFiles.get(`/nodes?ids=${decodeURIComponent(nodeId)}`);
return nodes[nodeId].document.children;
};
/**
* get svg image resource url
*
* @param {string} nodeId
* @return {Promise<string>}
*/
const getSvgImageUrl = async (nodeId) => {
const {data: {images, err}} = await instanceImages.get(`/?ids=${decodeURIComponent(nodeId)}&format=svg`);
return images[nodeId];
};
const getIconContent = async (url) => api.get(url);module.exports = {
getDocument,
getNode,
getNodeChildren,
getSvgImageUrl,
getIconContent,
};

Generate React Icon Component

Create a template for generating the JSX icon file:

// figma-import/utils/getIconJSXTemplate.jsmodule.exports = (name) => `
import React from 'react';
import {ReactComponent as ${name}Component} from './${name}.svg';import Icon from '../Icon';export const ${name} = (props) => (
<Icon {...props}>
<${name}Component/>
</Icon>
);
${name}.propTypes = Icon.propTypes;
`;

Let’s create an icon wrapper so that we can display any icon that we put in it. Also with wrapper, we can define a set of props with which we can change the parameters of the icon, for example, color and size values.

// src/components/Icons/Icon.jsximport React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import './Icon.css';const Icon = ({children, size = '100', color = 'dark', className}) => {
return (
<span className={classNames(
'icon',
`icon--size-${size}`,
`icon--color-${color}`, className)
}>
{children}
</span>
);
};
Icon.propTypes = {
size: PropTypes.oneOf('100', '200', '300'),
color: PropTypes.oneOf('dark', 'light', 'accent'),
className: PropTypes.string,
};
export default Icon;

Basic wrapper stylesheet:

// src/components/Icons/Icon.css.icon {
background-position: center;
background-repeat: no-repeat;
display: inline-block;
flex-shrink: 0;
position: relative;
vertical-align: middle;
}
.icon svg {
flex-shrink: 0;
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
}
...

Set the path to the folder where the icons will be located:

// figma-import/icons.jsconst IconsDir = path.resolve(__dirname, '../src/components/Icons');const getIconFolderPath = (name) => path.resolve(IconsDir, pascalCase(name));

Having received the name of the icon from the node and the JSX template, save the files:

// figma-import/icons.js/**
* generate icon component
* [iconName].jsx and [iconName].svg files
*
* @param iconNode
* @return {Promise<void>}
*/
const generateIcon = async (iconNode) => {
const iconUrl = await api.getSvgImageUrl(iconNode.id);
const iconName = pascalCase(iconNode.name);
const iconFolderPath = getIconFolderPath(iconName);
if (!fs.existsSync(iconFolderPath)) {
fs.mkdirSync(iconFolderPath);
}
const {data: iconContent} = await api.getImageContent(iconUrl);
fs.writeFileSync(path.resolve(iconFolderPath, `${iconName}.svg`), iconContent, {encoding: 'utf8'});
const iconJSXTemplate = getIconJSXTemplate(iconName);
fs.writeFileSync(path.resolve(iconFolderPath, `${iconName}.jsx`), iconJSXTemplate, {encoding: 'utf8'});
console.log(`${iconName} was written success!`);
};

As a result, we generate a list of imports of the received icons:

// figma-imports/icons.jsconst generateImports = (iconNodes) => {
const fileWithImportsPath = path.resolve(IconsDir, 'index.js');
const importsContent = iconNodes
.map(iconNode => {
const iconName = pascalCase(iconNode.name);
return `export * from './${iconName}/${iconName}';`;
})
.join('\n');
fs.writeFileSync(fileWithImportsPath, importsContent, {encoding: 'utf8'});console.log(`imports was written success!`);
};

Put it all together:

// figma-imports/icons.jsconst main = async () => {
clearIconsDir();
const iconNodesArr = await api.getNodeChildren(process.env.FRAME_WITH_ICONS_ID);iconNodesArr.forEach((iconNode) => {
generateIcon(iconNode);
});
await generateImports(iconNodesArr);
};

In the future, index.js is easy to expand with other scripts for importing entities from the Figma, for example, by importing tokens and local styles. However, we can leave that matter for another time in another article 😜.

// figma-imports/index.jsrequire('dotenv').config();const importIcons = require('./icons');const main = async () => {
await importIcons();
};
main().catch((err) => {
console.error('Unhandled rejection', err)
});

Add npm scripts:

// package.json"scripts": {
"figma-imports": "node ./figma-imports",
"prestart": "yarn figma-imports",
"start": "react-scripts start",
"build": "react-scripts build"
},

Example of using import icons in a project

You can find the full code example in the repository.

What can be improved?

  • Parallelize processes. This example was implemented in a synchronous style, for easier understanding. However, importing 5 icons requires a rather long time of about 10 seconds. This is because all work related to the generation of icons is performed sequentially. You can parallelize the processing of each icon and do it in several threads. The bluebird library will be able to help us perfectly in this. An example of asynchronous implementation is available here.
  • SVG optimization. Figma uses the SVG image optimization engine, but we can easily integrate additional SVG parse and optimization tools into our flow. The example with the SVGO library is available here.

Try it by yourself

You can find the full code example in the repo. Check all the branches.

Figma project example.

You can clone the repo and try the example on your Figma project.

Summary

The Figma API allows frontend developers and designers to work closely and make product development faster and better. As a result, we can automate a very routine part of your work and significantly simplify the process.

SVG icons delivery is clear evidence of this. We have achieved that all updates related to the icons come in a matter of seconds, you just have to run the script.

Thanks for answering my annoying questions and helping to understand the design process to UX/UI designer Anastasia Lapikova 👏.

Links

--

--

Developer, speaker and IT enthusiast. Passionate about typed languages, performance, scalable code and thoughtful design.