How to manage your Design Tokens with Style Dictionary

A reader, looking at the “24 min read” at the top of this page.

About Design Tokens

In a design system, people often use special entities called “design tokens” to store their “design decisions”. These entities are in the shape of key/value pairs, saved in specific file formats (usually JSON or YAML). These files are later used as input files, processed and transformed to generate different output files, with different formats, to be included and consumed by other projects and codebases. (If you want to know more about design tokens, I suggest to read here, here, here and here).

.button {
position: relative;
display: block;
width: 100%;
min-height: $token-button-height;
margin: 0;
padding: $token-spacing-sm $token-spacing-xxlg;
border-radius: $token-button-border-radius;
text-align: center;
}

// variants

.button--stroke {
border: $token-button-border-width solid currentColor;
background: transparent;
}

.button--monochrome {
border: $token-button-border-width solid $token-colour-gray;
background: #fff;
color: $token-colour-black;
}

// and other variants here
...
A portion of the “Tokens” page in our Cosmos design system

Migrating from Theo

When one year ago we introduced the design tokens in our design system, at that time the state of the art for design token management was Theo, a tool developed by the design system team at Salesforce — mainly Adam Putinski and Kaelig Deloumeau-Prigent (now at Shopify) — to manage their impressively large number of design tokens in the Lightning Design System.

{
"global": {
"type": "token",
"category": "brick"
},
"aliases": {
"TOKEN_BRICK_SIZE_SM": {
"value": "70px"
},
"TOKEN_BRICK_SIZE_MD": {
"value": "100px"
},
"TOKEN_BRICK_SIZE_LG": {
"value": "120px"
}
},
"props": {
"TOKEN_BRICK_SIZE_XXSM": {
"value": "36px"
},
"TOKEN_BRICK_SIZE_SM": {
"value": "{!TOKEN_BRICK_SIZE_SM}"
},
"TOKEN_BRICK_SIZE_MD": {
"value": "{!TOKEN_BRICK_SIZE_MD}"
},
"TOKEN_BRICK_SIZE_LG": {
"value": "{!TOKEN_BRICK_SIZE_LG}"
},
"TOKEN_BRICK_SIZE_XLG": {
"value": "150px"
}
}
}

First impact with Style Dictionary

I don’t remember exactly where I read for the first time of Style Dictionary, probably on my Twitter feed, or in some Medium blog post, or in a chat in the Design Systems Slack channel (Shaun Bent rightly made me notice that it was announced on February this year, but I don’t remember that post).

How the build process of Style Dictionary works under the hood (source)

Initial setup of Style Dictionary

Compared to other tools, I found it very easy to be up and running using Style Dictionary: in less than five minutes, I was already building my new design tokens.

style-dictionary init basic
style-dictionary build

Using Style Dictionary for your own project

At this point, you can start to play around with your token’s JSON files (and you config file) to see the different options that you have, in term of the organisation of your style properties and generation of different output formats. If you want, you can look at the examples folder included in Style Dictionary’s project repository to see more complex organisations and use cases, but I suggest to start with the “basic” one to avoid getting lost in the more advanced features offered by Style Dictionary (e.g. the distribution of assets like fonts or icons as “tokens” or the generation of tokens for React Native projects).

Documentation

Style Dictionary is a very well documented project. You can use the official website available at this address: https://amzn.github.io/style-dictionary/ or you can read directly the markdown files from the GitHub repo: https://github.com/amzn/style-dictionary/tree/master/docs.

The ”Category > Type > Item” classification

Style Dictionary has an implicit classification of the tokens:

{
"button": {
"border": {
"width": {
"value": "1px"
}
}
}
}
{
"button": {
"border": {
"width": {
"name": "button-border-width",
"value": "1px",
"original": {
"value": "1px"
},
"attributes": {
"category": "button",
"type": "border",
"item": "width"
},

"path": [
"button",
"border",
"width"
]
}
}
}
}

My design tokens organisation

Cosmos, our design system, has been built to support different “platforms” (Mobile Web, iOS and Android) and different “brands” (our application has white-label products).

├── config.json
├── src/
│ ├── brands/
│ │ │── brand_#1/
│ │ │ ├── color.json
│ │ │── brand_#2/
│ │ │ ├── color.json
│ │ │── brand_#3/
│ │ │ ├── color.json
│ ├── globals/
│ │ │── base/
│ │ │ ├── index.json
│ │ │── color/
│ │ │ ├── basic.json
│ │ │ ├── grayscale.json
│ │ │ ├── features.json
│ │ │ ├── social.json
│ │ │── icon/
│ │ │ ├── index.json
│ │ │── ...
│ │ │── spacing/
│ │ │ ├── index.json
│ │ │── typography/
│ │ │ ├── index.json
│ ├── platforms/
│ │ │── android/
│ │ │ ├── button.json
│ │ │ ├── ...
│ │ │ ├── typography.json
│ │ │── ios/
│ │ │ ├── button.json
│ │ │ ├── ...
│ │ │ ├── typography.json
│ │ │── mobile_web/
│ │ │ ├── button.json
│ │ │ ├── ...
│ │ │ ├── typography.json
├── dist/
│ │── android/
│ │ ├── ...
│ │── ios/
│ │ ├── ...
│ │── mobile_web/
│ │ │── brand_#1/
│ │ │ ├── **.scss
│ │ │ ├── **.js
│ │ │ ├── **.json
│ │ │── brand_#2/
│ │ │ ├── **.scss
│ │ │ ├── **.js
│ │ │ ├── **.json
│ │ │── brand_#3/
│ │ │ ├── **.scss
│ │ │ ├── **.js
│ │ │ ├── **.json
│ │── style_guide/
│ │ ├── android_brand_#1.scss
│ │ ├── android_brand_#1.json
│ │ ├── android_brand_#2.scss
│ │ ├── android_brand_#2.json
│ │ ├── ...
│ │ ├── ios_brand_#1.scss
│ │ ├── ios_brand_#1.json
│ │ ├── ios_brand_#2.scss
│ │ ├── ios_brand_#2.json
│ │ ├── ...
│ │ ├── mobile_web_brand_#1.scss
│ │ ├── mobile_web_brand_#1.json
│ │ ├── mobile_web_brand_#2.scss
│ │ ├── mobile_web_brand_#2.json
│ │ ├── ...
In our style guide, the user can switch between different platforms and brands.
├── component-library/
│ │ ...
│ ├── gift/
│ ├── icon/
│ ├── input/
│ │ ├── Input.js
│ │ ├── Input.scss
│ │ ├── Choice.js
│ │ ├── Search.js
│ │ ├── Toggle.js
│ ├── mark/
│ ├── modal/
│ │ ...
{
"input": {
"choice": {
"size": {
"value": "24px"
}
},
"toggle": {
"width": {
"value": "40px"
},
"height": {
"value": "24px"
}
},
"search": {
"height": {
"value": "32px"
}
}
}
}
$token-input-choice-size: 24px;
$token-input-toggle-width: 40px;
$token-input-toggle-height: 24px;
$token-input-search-height: 32px;
{
"alias": {
"colour": {
"brand": {
"primary": {
"value": "#bada22"
},
"secondary": {
"value": "#c0ffee"
}
}
}
}
}
{
"colour": {
"primary": {
"value": "{alias.colour.brand.primary.value}"
},
"secondary": {
"value": "{alias.colour.brand.secondary.value}"
},
...
}
}
{
"icon": {
"size": {
"xsm": { "value": "10px" },
"sm": { "value": "16px" },
"md": { "value": "22px" },
"lg": { "value": "30px" },
"xlg": { "value": "36px" },
"xxlg": { "value": "46px" }
},
"jumbo-size": {
"md": { "value": "{brick.size.md.value}" },
"lg": { "value": "{brick.size.lg.value}" }
}
}
}
{
"brick": {
"size": {
"xxsm": {...},
"sm": {...},
"md": { "value": "100px" },
"lg": { "value": "120px" },
"xlg": {...}
}
}
}
{
"button": {
"meta": {
"description": "design tokens for the button component"
},
"border": {
"width": {
"value": "1px",
"documentation": {
"comment": "the width of the border"
}
}
}
}
}
{
"button": {
"meta": {
"description": "design tokens for the button component"
},
"border": {
"width": {
"value": "1px",
"documentation": {
"comment": "the width of the border"
},
"original": {...},
"name": "button-border-width",
"attributes": {...},
"path": [...]
}
}
}
}
{
"button": {
"border": {
"width": {
"value": "1px",
"comment": "this is a comment"
}
}
}
}
$button-border-width: 1px; // this is a comment
You’re halfway through… keep going!

My (custom) build process

As I said above, the simplest way to build your design tokens with Style Dictionary is to run in the command line:

style-dictionary build
const SD = require('style-dictionary').extend('config.json');SD.buildAllPlatforms();
SD.buildPlatform('web');
const StyleDictionary = require('style-dictionary');const styleDictionary = StyleDictionary.extend( 'config.json' );// You can also extend with an object
// const styleDictionary = StyleDictionary.extend({ ... });
// you can call buildAllPlatforms or buildPlatform('...')
styleDictionary.buildAllPlatforms();
const StyleDictionaryPackage = require('./build-style-dictionary-package');
const getStyleDictionaryConfig = require('./build-style-dictionary-config');
console.log('Build started...');// PROCESS THE DESIGN TOKENS FOR THE DIFFEREN BRANDS AND PLATFORMS['web', 'ios', 'android'].map(function(platform) {
['brand#1', 'brand#2', 'brand#3'].map(function(brand) {
console.log('\n======================================');
console.log(`\nProcessing: [${platform}] [${brand}]`);
const StyleDictionary = StyleDictionaryPackage.extend(getStyleDictionaryConfig(brand, platform)); if (platform === 'web') {
StyleDictionary.buildPlatform('web/js');
StyleDictionary.buildPlatform('web/json');
StyleDictionary.buildPlatform('web/scss');
} else if (platform === 'ios') {
StyleDictionary.buildPlatform('ios');
} else if (platform === 'android') {
StyleDictionary.buildPlatform('android');
}
StyleDictionary.buildPlatform('styleguide');
console.log('\nEnd processing'); })
})
console.log('\n======================================');
console.log('\nBuild completed!');
function getStyleDictionaryConfig(brand, platform) {
return {
"source": [
`src/brands/${brand}/*.json`,
"src/globals/**/*.json",
`src/platforms/${platform}/*.json`
],
"platforms": {
"web/js": {
"transformGroup": "tokens-js",
"buildPath": `dist/web/${brand}/`,
"files": [
{
"destination": "tokens.es6.js",
"format": "javascript/es6"
}
]
},
"web/json": {
"transformGroup": "tokens-json",
"buildPath": `dist/web/${brand}/`,
"files": [
{
"destination": "tokens.json",
"format": "json/flat"
}
]
},
"web/scss": {
"transformGroup": "tokens-scss",
"buildPath": `dist/web/${brand}/`,
"files": [
{
"destination": "tokens.scss",
"format": "scss/variables"
}
]
},
"styleguide": {
"transformGroup": "styleguide",
"buildPath": `dist/styleguide/`,
"files": [
{
"destination": `${platform}_${brand}.json`,
"format": "json/flat"
},
{
"destination": `${platform}_${brand}.scss`,
"format": "scss/variables"
}
]
},
... // more declarations here
}
};
}
module.exports = getStyleDictionaryConfig;
function getStyleDictionaryConfig(brand, platform) {
return {
"source": [
`src/brands/${brand}/*.json`,
"src/globals/**/*.json",
`src/platforms/${platform}/*.json`
],
"platforms": {
"web/js": {
"transformGroup": "tokens-js",
"buildPath": `dist/web/${brand}/`,
"prefix": "token",
"files": [
{
"destination": "tokens.es6.js",
"format": "javascript/es6"
}
]
},
"web/json": {
"transformGroup": "tokens-json",
"buildPath": `dist/web/${brand}/`,
"prefix": "token",
"files": [
{
"destination": "tokens.json",
"format": "json/flat"
}
]
},
"web/scss": {
"transformGroup": "tokens-scss",
"buildPath": `dist/web/${brand}/`,
"prefix": "token",
"files": [
{
"destination": "tokens.scss",
"format": "scss/variables"
}
]
},
"styleguide": {
"transformGroup": "styleguide",
"buildPath": `dist/styleguide/`,
"prefix": "token",
"files": [
{
"destination": `${platform}_${brand}.json`,
"format": "json/flat"
},
{
"destination": `${platform}_${brand}.scss`,
"format": "scss/variables"
}
]
},
"ios": {
"transformGroup": "tokens-ios",
"buildPath": `dist/ios/${brand}/`,
"prefix": "token",
"files": [
{
"destination": "tokens-all-generic.plist",
"template": "ios/generic.plist"
},
...
]
},
"android": {
"transformGroup": "android",
"buildPath": `dist/android/${brand}/`,
"prefix": "token",
"files": [
{
"destination": "tokens-all-generic.xml",
"template": "android/generic"
},
...
]
}
}
};
}
const StyleDictionaryPackage = require('style-dictionary');// === CUSTOM FORMATS ===StyleDictionaryPackage.registerFormat({
name: 'json/flat',
formatter: function(dictionary) {
return JSON.stringify(dictionary.allProperties, null, 2);
}
});
// === CUSTOM TRANSFORMS ===StyleDictionaryPackage.registerTransform({
name: 'size/pxToPt',
type: 'value',
matcher: function(prop) {
return prop.value.match(/^[\d\.]+px$/);
},
transformer: function(prop) {
return prop.value.replace(/px$/, 'pt');
}
});
...// === CUSTOM TRANSFORM GROUPS ===StyleDictionaryPackage.registerTransformGroup({
name: 'styleguide',
transforms: ["attribute/cti", "name/cti/kebab", "size/px", "color/css"]
});
StyleDictionaryPackage.registerTransformGroup({
name: 'tokens-js',
transforms: ["name/cti/constant", "size/px", "color/hex"]
});
StyleDictionaryPackage.registerTransformGroup({
name: 'tokens-scss',
transforms: [ "name/cti/kebab", "time/seconds", "size/px", "color/css" ]
});
StyleDictionaryPackage.registerTransformGroup({
name: 'tokens-ios',
transforms: [ "attribute/cti", "name/cti/camel", "size/pxToPt"]
});
StyleDictionaryPackage.registerTransformGroup({
name: 'tokens-android',
transforms: [ "attribute/cti", "name/cti/camel", "size/pxToDp"]
});
// === CUSTOM TEMPLATES ===StyleDictionaryPackage.registerTemplate({
name: 'ios/generic.plist',
template: __dirname + '/templates/ios-generic.template'
});
...StyleDictionaryPackage.registerTemplate({
name: 'android/generic.xml',
template: __dirname + '/templates/android-generic.template'
});
...
  • you can override the names of the pre-defined functions (eg. you can register a size/px or a time/seconds function even if already exists)
  • you can use the prefix property to prepend a string to the tokens names
  • to get the list of pre-defined transform values in the transform groups (e.g. [‘attribute/cti’, ‘name/cti/pascal’, ‘size/rem’, ‘color/hex’] for the ‘JS’ group) you can do console.log(StyleDictionary.transformGroup[‘js’]) in your script and read the output in the command line shell
  • you can add custom meta-information to the tokens, and use it later in your filter/matcher functions and in the logic of your templates
  • you can add a filter declaration to a file block, to include only certain tokens values in the generated output files, based on some of their props (useful if you want to split the tokens into different files).

The generated design token files

We have seen how to set up a project with Style Dictionary and how to run a custom build. But what are the final results? How do the generated design token files look like? Well, here they are.

// ./dist/web/brand#1/tokens.scss$token-alias-color-brand-primary: #3B5998;
$token-alias-color-brand-secondary: #4267B2;
$token-avatar-size-xxsm: 36px;
...
// ./dist/web/brand#1/tokens.es6.jsexport const TOKEN_ALIAS_COLOR_BRAND_PRIMARY = '#3B5998';
export const TOKEN_ALIAS_COLOR_BRAND_SECONDARY = '#4267B2';
export const TOKEN_AVATAR_SIZE_XXSM = '36px';
...
// ./dist/web/brand#1/tokens.json[
{
"value": "#3B5998",
"type": "color",
"comment": "this is a comment",
"original": {
"value": "#3B5998",
"type": "color",
"comment": "this is a comment"
},
"name": "token-alias-color-brand-primary",
"attributes": {
"category": "alias",
"type": "color",
"item": "brand",
"subitem": "primary"
},
"path": [
"alias",
"color",
"brand",
"primary"
]
},
{
"value": "#4267B2",
"type": "color",
"comment": "this one is a comment too",
"original": {
"value": "#4267B2",
"type": "color",
"comment": "this one is a comment too"
},
"name": "token-alias-color-brand-secondary",
"attributes": {
"category": "alias",
"type": "color",
"item": "brand",
"subitem": "secondary"
},
"path": [
"alias",
"color",
"brand",
"secondary"
]
},
{
"value": "36px",
"original": {
"value": "36px"
},
"name": "token-avatar-size-xxsm",
"attributes": {
"category": "avatar",
"type": "size",
"item": "xxsm"
},
"path": [
"avatar",
"size",
"xxsm"
]
},
...
]
// ./dist/ios/brand#1/tokens-all.plist<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>tokenAliasColorBrandPrimary</key><string>#3B5998</string>
<key>tokenAliasColorBrandSecondary</key><string>#4267B2</string>
<key>tokenAvatarSizeXxsm</key><string>36pt</string>
...
</dict>
// ./dist/android/brand#1/tokens-all.xml<?xml version="1.0" encoding="UTF-8"?>
<resources>
<color name="token_alias_color_brand_primary">#3B5998</color>
<color name="token_alias_color_brand_secondary">#4267B2</color>
<item name="token_avatar_size_xxsm" type="dimen">36dp</item> ...
</resources>

The example project on Github

As I have already mentioned, I have created a repository with an example very similar to the way I have set up the design tokens for our Design System at Badoo. You can see the demo at this GitHub address:

  • the /src folder contains the JSON files used in input, each one of them containing the key/value/attributes tokens declarations
  • the /dist folder contains the generated files, in different formats for the different target platforms
  • the /templates folder contains the template files used to generate the files
  • the /build.js contains the entire build task, from the declarations of the custom functions, to the build configuration, to the actual build runner.

Conclusions/Final thoughts

I have already said a lot, so I’ll try to be concise at least in the conclusions.

THE END.

--

--

Design System Lead at HashiCorp. In love with Atomic Design, Design Systems & CSS architecture. Attendee/speaker/organiser of tech conferences and meetups.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Cristiano Rastelli

Cristiano Rastelli

Design System Lead at HashiCorp. In love with Atomic Design, Design Systems & CSS architecture. Attendee/speaker/organiser of tech conferences and meetups.