Table of Contents
1 | Introduction to Design Tokens
2 | Managing and Exporting Design Tokens With Style Dictionary
3 | Exporting Design Tokens From Figma With Style Dictionary
4 | Consuming Design Tokens From Style Dictionary Across Platform-Specific Applications
5 | Generating Design Token Theme Shades With Style Dictionary
6 | Documenting Design Tokens With Docusaurus
7 | Integrating Design Tokens With Tailwind
8 | Transferring High Fidelity From a Design File to Style Dictionary
9 | Scoring Design Tokens Adoption With OCLIF and PostCSS
10 | Bootstrap UI Components With Design Tokens And Headless UI
11 | Linting Design Tokens With Stylelint
12 | Stitching Styles to a Headless UI Using Design Tokens and Twind
What You’re Getting Into
I thought I was finished with my design tokens article series. However, I ended up using a combination of Style Dictionary and Tailwind in a side project, and I wanted to show you all the solution.
Tailwind is a popular tool for adding styles to a web application.
Tailwind shifts from writing your CSS in a global file, CSS modules, or inline JavaScript.
Instead, you compose the styles you need by applying “utility classes.” Utility classes are pre-defined classes that represent a style that you want to be applied to an element.
Here’s an example transformation from CSS to Tailwind’s utility classes.
button {
color: red;
padding: 1rem;
}
…becomes
<button class=“text-color-red p-4”>
Some Button
</button>
As you develop, you will have to check the Tailwind documentation to see which utility classes that you can use to apply the style you desire.
It takes some getting used to, but it gets easier and easier with time. There’s also a handy VSCode extension that provides some IntelliSense.
To summarize, Tailwind allows you to style your application by composing utility classes. A utility class applies a design specification, such as a color of red, to an element.
If you’ve been reading about design tokens, your Spidey senses should be tingling.
Each utility class effectively applies a design token, a key-value pair representing a design specification.
Integrating design tokens into Tailwind is therefore a natural fit.
Given that Tailwind allows you to override or extend its utility classes, we can map design tokens generated by Style Dictionary to utility classes.
In this article, we’ll get a taste of how we can do just that.
Integrating Design Tokens Into Tailwind
The Setup
In previous articles, I’ve written about how to automate the process of generating design tokens from a Figma file and transforming them with Style Dictionary and consuming the transformed design tokens across consuming appications.
By the time of reading this article, we’ve had enough practice with the automated process and can start this tutorial with an existing project that contains design tokens exported by Style Dictionary.
Here is a starter repository that you can fork to follow along with the rest
of the tutorial:
https://github.com/michaelmang/design-tokens-tailwind
The starter repository contains a simple React app with the transformed tokens
stored in tokens/tokens.js
.
/**
* Do not edit directly
* Generated on Mon, 15 Feb 2021 16:35:23 GMT
*/
module.exports = {
"color": {
"primary": {
"value": "#3a4d7e",
"type": "color",
"original": {
"value": "#3A4D7E",
"type": "color"
},
"name": "colorPrimary",
"attributes": {
"category": "color",
"type": "primary"
},
"path": [
"color",
"primary"
]
},
"primary_light": {
"value": "#b9c4df",
"type": "color",
"original": {
"value": "#B9C4DF",
"type": "color"
},
"name": "colorPrimaryLight",
"attributes": {
"category": "color",
"type": "primary_light"
},
"path": [
"color",
"primary_light"
]
},
"black": {
"value": "#29322e",
"type": "color",
"original": {
"value": "#29322E",
"type": "color"
},
"name": "colorBlack",
"attributes": {
"category": "color",
"type": "black"
},
"path": [
"color",
"black"
]
},
"black_light": {
"value": "#404f48",
"type": "color",
"original": {
"value": "#404F48",
"type": "color"
},
"name": "colorBlackLight",
"attributes": {
"category": "color",
"type": "black_light"
},
"path": [
"color",
"black_light"
]
},
"white": {
"value": "#ffffff",
"type": "color",
"original": {
"value": "#FFFFFF",
"type": "color"
},
"name": "colorWhite",
"attributes": {
"category": "color",
"type": "white"
},
"path": [
"color",
"white"
]
},
"white_dark": {
"value": "#f1f5f8",
"type": "color",
"original": {
"value": "#F1F5F8",
"type": "color"
},
"name": "colorWhiteDark",
"attributes": {
"category": "color",
"type": "white_dark"
},
"path": [
"color",
"white_dark"
]
},
"error": {
"value": "#eb6957",
"type": "color",
"original": {
"value": "#EB6957",
"type": "color"
},
"name": "colorError",
"attributes": {
"category": "color",
"type": "error"
},
"path": [
"color",
"error"
]
},
"warning": {
"value": "#f2c94c",
"type": "color",
"original": {
"value": "#F2C94C",
"type": "color"
},
"name": "colorWarning",
"attributes": {
"category": "color",
"type": "warning"
},
"path": [
"color",
"warning"
]
},
"info": {
"value": "#aac2ff",
"type": "color",
"original": {
"value": "#AAC2FF",
"type": "color"
},
"name": "colorInfo",
"attributes": {
"category": "color",
"type": "info"
},
"path": [
"color",
"info"
]
}
}
};
These design tokens could be generated from a JSON representation like this:
{
"color": {
"primary": {
"value": "#3A4D7E",
"type": "color"
},
"primary_light": {
"value": "#B9C4DF",
"type": "color"
},
"black": {
"value": "#29322E",
"type": "color"
},
"black_light": {
"value": "#404F48",
"type": "color"
},
"white": {
"value": "#FFFFFF",
"type": "color"
},
"white_dark": {
"value": "#F1F5F8",
"type": "color"
},
"error": {
"value": "#EB6957",
"type": "color"
},
"warning": {
"value": "#F2C94C",
"type": "color"
},
"info": {
"value": "#AAC2FF",
"type": "color"
}
}
}
By
configuring Style Dictionary
to use the
js transform group
and the
javascript/module formatting, we get the final tokens as seen in
src/tokens/tokens.js
of the project.
The javascript/module
format exposes a
CommonJS module. We will have to prefer this format over the
javascript/es6
format since Tailwind’s
configuration works with CommonJS modules.
This is too bad because the
javascript/es6
format is preferable given
that it exports a JavaScript key-value pair that reflects a design token more
closely.
I have already initialized the project with Tailwind which you can easily do following the documentation for installing with Create React App.
The Implementation
For our tutorial, we are going to override the default colors that come with
Tailwind with the tokens that are in the
tokens.js
file.
The place where we can override Tailwind is in the
tailwind.config.js
file.
If we want to override the theme colors, we would add a
colors
property with nested properties
representing the colors:
module.exports = {
// ...
theme: {
colors: {
primary: '...',
secondary: '...',
// all other colors
},
extend: {},
},
// ...
}
We won’t be doing this, but alternatively, you could nest this
colors
object within the
extend
object if you wanted to add more
colors while keeping
the defaults.
Each property within colors
is essentially a
design token. So, we’ll place our design tokens there.
Since both this file and our tokens are JavaScript, we can import our tokens
into the tailwind.config.js
file and set them
as the value of the colors
property.
Let’s try by just importing the tokens and logging what we are working with:
const tokens = require('./tokens/tokens');
console.log(tokens);
Run node tailwind.config.js
and we can see
that we do in fact have access to all the tokens. We will need to transform
this object to just be an object with key-value pairs like:
{
primary: '#3a4d7e',
// ...
}
Given the structure of the current tokens object, we will need to have our new
object use the attributes.type
value nested
under each color property (i.e.
color.primary
,
color.secondary
) as the keys and the value on
each color property as the value.
Note, I’m suggesting that we use the
attributes.type
in place of the
name
for each color property since we don’t
need the “color” substring to be included. Practically, this means we can
reference a color like text-primary
as
opposed to text-color-primary
which is how
the Tailwind defaults work.
Back in our tailwind.config.js
file, let’s
write some code that iterates through the imported
tokens
object and creates a new object with
the desired format we just described.
First, we can transform our object into a collection of the color property values:
const tokens = require('./tokens/tokens');
const colors = Object.values(tokens.color);
This is what colors
contains:
[
{
value: '#3a4d7e',
type: 'color',
original: { value: '#3A4D7E', type: 'color' },
name: 'colorPrimary',
attributes: { category: 'color', type: 'primary' },
path: [ 'color', 'primary' ]
},
{
value: '#b9c4df',
type: 'color',
original: { value: '#B9C4DF', type: 'color' },
name: 'colorPrimaryLight',
attributes: { category: 'color', type: 'primary_light' },
path: [ 'color', 'primary_light' ]
},
// ...
]
Now, let’s iterate through this collection and transform each object into a
entries ([key, value]
pairs). Again,
the key is the attributes.type
and the value
is the value
.
const tokens = require('./tokens/tokens');
const colors = Object
.values(tokens.color)
.map(({ attributes, value }) => [
attributes.type, value
]);
colors
now contains:
[
[ 'primary', '#3a4d7e' ],
[ 'primary_light', '#b9c4df' ],
[ 'black', '#29322e' ],
[ 'black_light', '#404f48' ],
[ 'white', '#ffffff' ],
[ 'white_dark', '#f1f5f8' ],
[ 'error', '#eb6957' ],
[ 'warning', '#f2c94c' ],
[ 'info', '#aac2ff' ]
]
Next, we can transform these entries into an object by using Object.entries:
const tokens = require('./tokens/tokens');
const colors = Object.fromEntries(Object
.values(tokens.color)
.map(({ attributes, value }) => [
attributes.type, value
]));
colors
is now the expected format for
Tailwind:
{
primary: '#3a4d7e',
primary_light: '#b9c4df',
black: '#29322e',
black_light: '#404f48',
white: '#ffffff',
white_dark: '#f1f5f8',
error: '#eb6957',
warning: '#f2c94c',
info: '#aac2ff'
}
Finally, we’ll need to make sure that the keys are in kebab case:
const kebabcase = require('lodash.kebabcase');
const tokens = require('./tokens/tokens');
const colors = Object.fromEntries(Object
.values(tokens.color)
.map(({ attributes, value }) => [
kebabcase(attributes.type), value
]));
Make sure to install kebabcase:
yarn add lodash.kebabcase
Finally, we can nest this new object within the
theme
object of the Tailwind config:
const kebabcase = require('lodash.kebabcase');
const tokens = require('./tokens/tokens');
const colors = Object.fromEntries(Object
.values(tokens.color)
.map(({ attributes, value }) => [
kebabcase(attributes.type), value
]));
module.exports = {
purge: [],
darkMode: false, // or 'media' or 'class'
theme: {
colors,
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
As a final test, we can update the
src/App.js
file and try to utilize a Tailwind
utility classes that uses the colors from our configuration:
- <header className="App-header">
+ <header className="bg-black-light">
Let’s run npm start
and test these changes!
Woohoo! We’ve successfully integrated design tokens from Style Dictionary with Tailwind. 🎉
Here's the final code:
https://github.com/michaelmang/design-tokens-tailwind/pull/1
Conclusion
I hope this article helps you put a foot forward towards integrating Tailwind within a design tokens pipeline.
In my next article, I propose an alternative to the normal design to developer workflow as I've described so far.
I’ll be releasing the articles as an ebook with some additional goodies. Subscribing to the newsletter will be the best way to get notified if you are interested.
As always, discuss, pow, and share!