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

.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

{
"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

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

Initial setup of Style Dictionary

style-dictionary init basic
style-dictionary build

Using Style Dictionary for your own project

Documentation

The ”Category > Type > Item” classification

{
"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

├── 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

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

// ./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

  • 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

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.

More from Medium

Design System in Frontend

Why You Need A Design System (part 2)

Sparking Engagement: Redesigning AdvocacyDenver

A brightly lit photo of an adult holding an infant’s hand with their pointer finger.

Visual design essentials

An image of visual deign tools