We all know the benefit of design systems. As a designer we want to have a consistent UI over web, mobile and any other platform the brand is present. As a developer we do not want to spend time exporting assets and colors from Figma, importing them into the code and repeating this process every time something changes.

Luckily there exists a tool to automate this process. Specify is a cloud platform which stores a single source of truth for your design tokens (text styles, colors, icons, imagery, etc.) and distributes them to the different platforms. Specify allows you to import your tokens from a source like Figma (and soon other sources like Google Drive or Dropbox) and keep them in sync while you make changes to the source. You can view the tokens in the web app and export them to GitHub or to your project using the CLI.

In this blogpost we will focus on syncing the design tokens from Figma to React Native.

Figma to Specify

From Specify to React Native

Syncing design tokens from Figma to Specify is just a matter of clicking a few buttons. The Specify team has written a clear guide on how to set it up, and Specify will automatically sync any new changes made to the Figma file. I recommend taking a look at the example Figma file to see how each design token should be delivered.

Specify to React Native

From Specify to React Native

To pull design tokens into a project, Specify offers a CLI with a pull and sync command. The pull command will import all design tokens, where the sync command will only update the ones that are already present. The CLI uses a .specifyrc config file (JS or JSON) in which you can write rules, which are different jobs to run in parallel.

// .specifyrc.js
module.exports = {
repository: '@my_organization/my-repository',
personalAccessToken: 'my_personal_access_token',
rules: [
// different jobs in parallel
],
};

For each rule we can use a different filter, such that the rule only applies to certain types of design tokens. Next to filters, we can also use parsers, which will manipulate the raw design tokens (which are in JSON format) and output a format that suits the programming language and platform more. Parsers behave like a pipeline, where each parser receives the input of the previous one. There are a lot of parsers to choose from, some perform very low level transformations such as round-number and camelcasify, but also full-blown parsers to a single technology such as to-tailwind and to-react-native.

Downloading fonts & assets

If we filter on assets ('vector', 'bitmap' and 'font'), Specify will download the assets themselves instead of the JSON describing them. They will be placed under the given folder (here src/common/assets).

For SVG's, we can use the svgo parser, which optimizes the SVG code and outputs a minified result. The default svgo settings are pretty good, but we add removeDimensions to make them responsive and replace the hard coded colors with "currentColor" such that we can color them with React Native styles.

// .specifyrc.js
...
rules: [
{
name: "🖊 Download Vector Assets",
path: "src/common/assets/vector",
filter: {
types: ["vector"],
},
parsers: [
{ name: "camelcasify" },
{
name: "svgo",
options: {
svgo: {
plugins: [
{ removeDimensions: true },
{ convertColors: { currentColor: true } },
],
},
},
},
],
},
{
name: "🏞 Download Bitmap Assets",
path: "src/common/assets/bitmap",
filter: { types: ["bitmap"] },
parsers: [
{ name: 'camelcasify' },
{
// This parser will give @2x and @3x variants their correct file name
name: 'name-assets-files-by-pattern',
options: {
pattern: '@x.',
},
},
],
},
{
name: "🔡 Download Fonts",
path: "src/common/assets/fonts",
filter: { types: ["font"] },
},
],
...

Syncing desing tokens

Now that assets and fonts are downloaded, we are ready to import the rest of the design system. For this we will use the to-react-native parser. It will generate a JavaScript (or TypeScript) theme object with all the design tokens in the correct format for React Native to understand.

// .specifyrc.js
...
{
name: '⚛️ Import tokens',
path: 'src/common/theme/theme.js',
parsers: [
{
name: 'to-react-native',
options: {
assetsFolderPath: {
bitmap: 'src/common/assets/bitmap',
vector: 'src/common/assets/vector',
},
},
},
],
},
...

If you have questions or notice any issues with the to-react-native parser, feel free to contribute or talk to me about it 🙏

An example of a full .specifyrc.js file can be found here. The output file will look something like this:

// src/common/theme/theme.js
import assetChevronRight from '../assets/vector/chevronRight.svg';
import assetLogo from '../assets/bitmap/logo.png';

const theme = {
bitmap: {
logo: assetLogo,
},
vector: {
chevronRight: assetChevronRight,
},
textStyle: {
body: {
fontWeight: '500',
fontSize: 16,
lineHeight: 24,
fontFamily: 'Montserrat-Medium',
color: 'rgb(0, 0, 0)',
},
},
measurement: {
grid: 8,
},
color: {
primary500: 'rgb(0, 0, 153)',
},
gradient: {
sunset: [
{
angle: 90,
colors: ['rgb(245, 72, 63)', 'rgb(255, 142, 5)'],
locations: [0, 1],
},
],
},
opacity: {
dimmed: 0.6,
},
font: {
montserratMedium: 'Montserrat-Medium',
},
};

export default theme;

As a final step we will add an npm script in our package.json:

// package.json
"scripts": {
...
"specify:pull": "specify pull -C .specifyrc.js",
},

Now we have our Figma project synced with React Native 🎉. Every time the designer changes a color or adds an icon, we can run npm run specify:pull and see the changes in our app!

Using the theme object

Making use of the design tokens is fairly straighforward for most types.

Bitmaps

Simple as that!

import { Image } from 'react-native';
import theme from 'common/theme';

const App = () => <Image source={theme.bitmap.logo} />;

Vectors

All vector assets in theme.js are imported directly. If you are not using Expo, you will need to install and configure react-native-svg-transformer to resolve the import statements.

Unlike bitmaps, the imported assets are React elements. We create a Vector component to make the usage look more like regular images:

const Vector = ({ source: SVGElement, ...props }) => <SVGElement {...props} />;

const App = () => (
<>
{/* a bit weird */}
<theme.vector.chevronRight />
{/* much better! */}
<Vector source={theme.vector.chevronRight} />
</>
);

Fonts

This part is specific to non-Expo projects. For Expo apps, it could be different.

In the "Download Fonts" rule above, the downloaded .ttf files will be under src/common/assets/fonts/, but they should also be imported in the native projects as well. This can be done by creating a react-native.config.js

// react-native.config.js
module.exports = {
project: {
ios: {},
android: {},
},
assets: ['src/common/assets/fonts/'],
dependencies: {
// Optionally.
// Prevent `react-native-vector-icons`
// from linking all fonts
'react-native-vector-icons': {
assets: [],
},
},
};

Running react-native link will link all fonts under that path automatically to the Android and iOS project! We can run it after every specify pull job:

// package.json
"scripts": {
...
- "specify:pull": "specify pull -C .specifyrc.js",
+ "specify:pull": "specify pull -C .specifyrc.js && react-native link",
},

Gradients

The to-react-native parser transforms gradients into a format which is easy to use with react-native-linear-gradient. Example usage:

const App = () => (
<LinearGradient
colors={theme.gradients.myGradient.colors}
locations={theme.gradients.myGradient.locations}
useAngle
angle={theme.gradients.myGradient.angle}
/>

);

Conclusion

Specify is a young but promising tool to sync Figma files to all you different platforms. If you feel like there is a parser missing or want to add some options, feel free to contribute to the project. The Specify team is very responsive and is open to suggestions.