38
loading...
This website collects cookies to deliver better user experience
dp
dimensions for Android development, or a Sketch colour palette file) by automated tooling. The idea is that whether you're making a website, Android app, slide deck, or print brochure you can always consume this JSON or YAML file, transform the design tokens to the format that suits your project, and avoid any hard-coded values or manual transformations.Design tokens are the visual design atoms of the design system — specifically, they are named entities that store visual design attributes. We use them in place of hard-coded values (such as hex values for color or pixel values for spacing) in order to maintain a scalable and consistent visual system for UI development.
# Make sure you're using a recent and supported version
# of Node. I like to use nvm to manage my Node versions.
nvm use --lts
# We can track our changes with Git.
git init
# Create a package.json file by answering some prompts:
npm init
# Install Tailwind, its dependencies, and create
# a basic Tailwind config file.
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init
# Install Style Dictionary.
npm install -D style-dictionary
style-dictionary.config.js
file with a single design token, just so we can experiment with it:module.exports = {
tokens: {
color: {
primary: { value: '#fbbf24' },
},
},
platforms: {
scss: {
transformGroup: 'scss',
buildPath: 'src/scss/',
files: [
{
destination: '_variables.scss',
format: 'scss/variables',
},
],
},
},
};
package.json
's scripts
object, to allow us to tell Style Dictionary to do its thing:"scripts": {
"style-dictionary:build": "style-dictionary build --config ./style-dictionary.config.js"
},
npm run style-dictionary:build
we should end up with a new file created at src/scss/_variables.scss
with this inside it:// Do not edit directly
// Generated on Tue, 06 Jul 2021 20:21:18 GMT
$color-primary: #fbbf24;
style-dictionary.config.js
file. We can write the CSS variables straight to our dist
directory, ready to link to in our application:module.exports = {
// …
platforms: {
// …
css: {
transformGroup: 'css',
buildPath: 'dist/css/',
files: [
{
format: 'css/variables',
destination: 'variables.css',
},
],
},
},
};
dist/css/variables.css
file that contains this content:/**
* Do not edit directly
* Generated on Tue, 06 Jul 2021 20:25:36 GMT
*/
:root {
--color-primary: #fbbf24;
}
import resolveConfig from 'tailwindcss/resolveConfig'
import tailwindConfig from './tailwind.config.js'
const fullConfig = resolveConfig(tailwindConfig)
fullConfig.theme.width[4]
// => '1rem'
fullConfig.theme.screens.md
// => '768px'
fullConfig.theme.boxShadow['2xl']
// => '0 25px 50px -12px rgba(0, 0, 0, 0.25)'
const resolveConfig = require('tailwindcss/resolveConfig');
const tailwindConfig = require('./tailwind.config.js');
const { theme } = resolveConfig(tailwindConfig);
const tokens = { color: theme.colors };
console.log(tokens);
module.exports = {
tokens,
platforms: {
scss: {
transformGroup: 'scss',
buildPath: 'src/scss/',
files: [
{
destination: '_variables.scss',
format: 'scss/variables',
},
],
},
css: {
transformGroup: 'css',
buildPath: 'dist/css/',
files: [
{
format: 'css/variables',
destination: 'variables.css',
},
],
},
},
};
value
property, like this:color: {
background: {
transparent: { value: "transparent" }
}
}
tokens
object to match that format before we pass it to Style Dictionary. sm: '640px'
for a screens
value. Colours can be a key/pair like transparent: 'transparent'
but also a key and object containing different shades, like this:"indigo": {
"50": "#eef2ff",
"100": "#e0e7ff",
"200": "#c7d2fe",
"300": "#a5b4fc",
"400": "#818cf8",
"500": "#6366f1",
"600": "#4f46e5",
"700": "#4338ca",
"800": "#3730a3",
"900": "#312e81"
}
sans: [
'ui-sans-serif',
'system-ui',
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'"Noto Sans"',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
'"Noto Color Emoji"',
],
map
and looked smart but was utterly unreadable. Another made a lot of use of forEach
but I needed to mess around with Object.fromEntries
and Object.entries
to convert my object into an array to loop over. Hooray for no dependencies, but it was a bit of a song and a dance! Eventually, I fell back to my old friend, Lodash. Not the trendiest of options, but it works, and it'll be understandable to a wider audience.const resolveConfig = require('tailwindcss/resolveConfig');
const tailwindConfig = require('./tailwind.config.js');
const _ = require('lodash');
// Grab just the theme data from the Tailwind config.
const { theme } = resolveConfig(tailwindConfig);
// Create an empty object to hold our transformed tokens data.
const tokens = {};
// A helper function that uses Lodash's setWidth method to
// insert things into an object at the right point in the
// structure, and to create the right structure for us
// if it doesn't already exist.
const addToTokensObject = function (position, value) {
_.setWith(tokens, position, { value: value }, Object);
};
// Loop over the theme data…
_.forEach(theme, function (value, key) {
switch (key) {
case 'fontFamily':
// Font family data is in an array, so we use join to
// turn the font families into a single string.
_.forEach(theme['fontFamily'], function (value, key) {
addToTokensObject(
['fontFamily', key],
theme['fontFamily'][key].join(',')
);
});
break;
case 'fontSize':
// Font size data contains both the font size (makes
// sense!) but also a recommended line-length, so we
// create two tokens for every font size, one for the
// font-size value and one for the line-height.
_.forEach(theme['fontSize'], function (value, key) {
addToTokensObject(['fontSize', key], value[0]);
addToTokensObject(
['fontSize', `${key}--lineHeight`],
value[1]['lineHeight']
);
});
break;
default:
_.forEach(value, function (value, secondLevelKey) {
if (!_.isObject(value)) {
// For non-objects (simple key/value pairs) we can
// add them straight into our tokens object.
addToTokensObject([key, secondLevelKey], value);
} else {
// Skip 'raw' CSS media queries.
if (!_.isUndefined(value['raw'])) {
return;
}
// For objects (like color shades) we need to do a
// final forOwn loop to make sure we add everything
// in the right format.
_.forEach(value, function (value, thirdLevelKey) {
addToTokensObject([key, secondLevelKey, thirdLevelKey], value);
});
}
});
break;
}
});
t-
prefix to all my exported tokens, to help prevent collisions with any existing code or third-party code.const tokenPrefix = 't-';
const limitedFilter = (token) =>
['colors', 'spacing', 'fontFamily'].includes(token.attributes.category);
const fullFilter = (token) =>
[
'screens',
'colors',
'spacing',
'opacity',
'borderRadius',
'borderWidth',
'boxShadow',
'fontFamily',
'fontSize',
'fontWeight',
'letterSpacing',
'lineHeight',
'maxWidth',
'zIndex',
'scale',
'transitionProperty',
'transitionTimingFunction',
'transitionDuration',
'transitionDelay',
'animation',
].includes(token.attributes.category);
module.exports = {
tokens,
platforms: {
scss: {
transformGroup: 'scss',
prefix: tokenPrefix,
buildPath: 'src/scss/',
files: [
{
format: 'scss/variables',
destination: '_variables.scss',
filter: fullFilter,
},
],
},
css: {
transformGroup: 'css',
prefix: tokenPrefix,
buildPath: 'dist/css/',
files: [
{
format: 'css/variables',
destination: 'variables.css',
filter: limitedFilter,
},
],
},
},
};
js: {
transformGroup: 'js',
prefix: tokenPrefix,
buildPath: 'dist/js/',
files: [
{
format: 'javascript/module',
destination: 'tokens.js',
filter: fullFilter,
options: {
outputReferences: true,
},
},
],
},