Design tokens and styleguides with Eleventy
Lately I’ve been exploring the concept of design tokens as a way to make design decisions more portable and easier to maintain.
If you‘re new to design tokens, here’s a good explanation from Robin Rendle.
”Design tokens are an agnostic way to store variables such as typography, color, and spacing so that your design system can be shared across platforms like iOS, Android, and regular ol’ websites.”
There’s some great tools out there that help you generate design tokens. Theo used by the Salesforce team is a popular choice, as is Chromatic and Style Dictionary. However, for my particular needs, most of theses solutions are either overkill or not exactly what I was looking for.
All I want to do is start with some basic tokens which are stored in their own file, independent of whatever will eventually consume them. Then I’d like to make these tokens accessible in my stylesheets and templates. By doing this I’ll be able to use my tokens to style the website and build a styleguide to visually represent them.
When my tokens are updated at the source, I want these changes to cascade down to the styleguide template, SCSS styles and whatever else in the website uses them.
I’m not interested in generating tokens for IOS or Android, like a lot of these existing tools offer. This is one of the reasons I decided to roll my own. All I’m after is a simple workflow that does the above with minimal fuss.
All this should be possible with a few NPM packages, NPM scripts and Eleventy.
Setting up the project #
First thing’s first. I need to create a new directory for my website, then initialise NPM from inside the directory, so I can use some NPM packages.
npm init -y
I now have a package.json
file at the root of my project.
Next, I need to install Eleventy so I can process my templates when I add them. I won’t go through this in detail as I’ve already covered that in Getting Started with Eleventy.
But here are a few reminders.
Install Eleventy:
npm install --save-dev @11ty/eleventy
Run Eleventy:
npx @11ty/eleventy
Start a local web server:
npx @11ty/eleventy --serve
You might want to add an index.njk
file at the root of the project with some boilerplate code. This will give Eleventy something to process.
Starting with YAML #
From what I’ve read it sounds like creating design tokens in YAML is a good place to start. Makes sense. YAML is easy to read and write by anyone, so I can see the benefits. Should also play nice with Forestry or Netlify CMS.
Start by creating a file inside the project at src/tokens/tokens.yaml
. This will be the source of truth, where I’ll write my design tokens and manage any future changes before they are generated.
To keep things simple I‘ll add a few color tokens and a sizing scale to the file. This should be more than enough to get going with for now.
colors:
primary: "tomato"
background: "#f1f1f1"
body: "#333"
size-scale:
sm: "0.8rem"
md: "1rem"
lg: "1.4rem"
xl: "2rem"
I’ll return to this file later. Now I need to think about how to convert these tokens into a format that can be consumed from inside Eleventy templates.
Converting YAML to JSON #
Eleventy works well with JSON. You can pass it to templates which is ideal. So next it makes sense to take these YAML tokens and convert them into JSON.
You can do this with the YAMLJS package.
npm install yamljs --save-dev
Now inside package.json
file, write a quick NPM script:
"tokens:json": "yaml2json src/tokens/tokens.yaml > src/site/_data/tokens.json --pretty"
So what’s going on here? First, I’m telling Yaml2json the source folder of my tokens, then I specify the destination folder where the JSON output should be stored. I’m adding the --pretty
flag to make the JSON easier to read.
For this to work out I need to add a _data
folder at ./src/data
, so that the script will run and generate a tokens.json
file inside the folder.
Now, when I type npm run tokens:json
in the command line, YAMLJS will take my tokens.yaml
file, process it as JSON, then chuck it in the _data
folder.
The _data
folder is where Eleventy suggests storing data files by default, but you could easily store this wherever you like. Just change the data location in your config file to do so.
Consuming JSON in templates to crete a style guide #
I now have a new file called tokens.json
inside my _data
folder that looks like this.
{
"colors": {
"primary": "#542bff",
"background": "#f1f1f1",
"body": "#333"
},
"size-scale": {
"sm": "0.8rem",
"md": "1rem",
"lg": "1.4rem"
}
}
This is an exact representation of my tokens in JSON format. The next step is to consume these tokens in a template.
You might want to do this for a number of reasons, but perhaps the most obvious one is to build a visual representation of your tokens. For the sake of simplicity, I’m refering to this as a styleguide.
To keep things nice and modular, I start by creating a new file for my styleguide which I’ll be able to add to my index file (or wherverer I like) later as an include.
touch src/_includes/styleguide.njk
Then I add the following code to loop through the JSON object, and return each item.
<section>
<!-- Loop through JSON object -->
{% for key, item in tokens.colors %}
<div class="color">
<p class="color-palette" style="background-color: {{ item }}">Some space</p>
<p class="color-hex">{{ item }}</p>
</div>
{% endfor %}
</section>
You can return a specific item value like this.
<!-- Grab the specific value -->
<div style="background-color:{{ tokens.colors.primary }}">
{{ tokens.colors.primary }}
</div>
Pretty simple.
Converting JSON to SCSS #
Now I need a way to consume my tokens from within CSS files. That means turning this JSON output into SCSS.
You can use the conveniently named json-to-scss package to make this happen.
npm install json-to-scss --save-dev
With the package installed, write another NPM script inside the scripts object in package.json
, underneath the last one.
"json:scss": "json-to-scss src/_data/tokens.json src/scss/_tokens.scss"
Here I’m telling json-to-sass
the source folder and the destination for the sass variables.
Now I can run npm run json:scss
in the command line and json-to-scss
will take our JSON, convert it to SCSS and chuck it in src/scss/_tokens.scss
ready for my stylesheets to consume.
Now I have a new file at ./src/scss/tokens.scss
containing a Sass map of my tokens.
That look something like this:
$tokens: (
colors: (
primary: #542bff,
background: #f1f1f1,
body: #333,
),
size-scale: (
sm: 0.8rem,
md: 1rem,
lg: 1.4rem,
),
);
So far so good.
Mapping through SCSS to create variables and styles #
At this point I have a Sass map which stores my tokens. But I need to do something with the data so I can use it in my styles.
It makes sense to extract the data and turn it into CSS custom properties. I’ll want to create a custom property for every single value in the map.
Something like this:
--color-primary: #542bff;
--color-background: #f1f1f1;
--color-body: #333;
--size-scale-sm: 0.8rem;
--size-scale-md: 1rem;
--size-scale-lg: 1.4rem;
To do this I create a new Sass file at src/scss/_config.scss
.
From inside _config.scss
, first I import the tokens map, then grab each property type in the array and assign each to a corresponding variable.
@import "tokens";
$colors: map-get($tokens, "colors");
$size-scale: map-get($tokens, "size-scale");
Now to generate the custom properties.
To do this I’ll loop through the map array, which can be done using the @each directive. I want to create these properties on the :root
element so I have them available throughout my website.
There are a couple of methods you can use to make this happen.
The first method:
:root {
@each $color-name, $color-value in $colors {
--color-#{$color-name}: #{$color-value};
}
}
The second method:
:root {
@each $color in $colors {
#{'--color-' + nth($color, 1)}: #{nth($color, 2)};
}
}
Both methods produce the same results, so choose whichever approach you are more comfortable with and make most sense of.
You can do exactly the same thing for the size scale.
:root {
@each $size in $size-scale {
#{'--size-' + nth($size, 1)}: #{nth($size, 2)};
}
}
Compiling SCSS to CSS #
Now I want to compile my SCSS to CSS, so that the browser can make use of my styles and I can see a visual output of my tokens as CSS custom properties.
To do this I’m installing the node-sass package.
npm install node-sass --save-dev
Then I’ll write a new NPM script to take the SCSS from src/scss
and compile it to CSS inside src/site/_includes/css
where Eleventy will be able to process it.
"scss": "node-sass --output-style expanded -o src/_includes/css src/scss"
If you want to get a better understanding of what’s happening here, I cover this in detail in Getting Started with Eleventy.
Before testing the script I need to add an entry point for my SCSS. So I create a main.scss
file at /src/scss/main.scss
and import the config file.
@import "config";
Now I can test the script by running:
npm run scss
A new file has now been created at src/_includes/css/main.css
containing my tokens as CSS custom properties.
:root {
--color-primary: tomato;
--color-background: #f1f1f1;
--color-body: #333;
--size-sm: 0.8rem;
--size-md: 1rem;
--size-lg: 1.4rem;
--size-xl: 2rem;
}
Exactly what I was after.
Watching files for changes #
Theres a few final things left to do before this workflow is complete. The first thing is to set up a script that will watch for any changes made to SCSS files.
When I make a change to my SCSS, I need a way to run the SCSS script I just wrote, so I don’t have to do it manually each time. You can use a package called onchange to make this happen.
First install the package.
npm install onchange --save-dev
Then write a script to run the SCSS script, when changes are made to SCSS files.
"watch:css": "onchange 'src/scss' -- npm run scss"
All we’re saying here is when npm run watch:css
is run in the terminal, onchange will “watch” for changes in the src/scss
directory, then run the scss
script when it sees changes occur.
Now there’s one final problem to solve. To get a development server running and the SCSS compiling to css onchange, right now you have to run two scripts: watch:css
(the one we just created) and eleventy --serve
.
You can combine these into one script to make life easier, but first create a simple serve script.
"serve": "eleventy --serve",
Nice. That simplifies the Eleventy serve command. Now I need to install one final package and we’re good to go.
Run all scripts in parallel #
npm-run-all will help you run serve
and watch:css
in parallel, alongside our other two scripts; tokens:json
and json:scss
.
First install the package.
npm install npm-run-all --save-dev
Then write a start script which runs all the scripts at the same time.
"start": "run-p tokens:json json:scss serve watch:css"
Now you should be able to run npm start
in the terminal and everything should work as expected.
One thing to note, if you make changes in your tokens.yaml
you will need to stop the server and re-run npm start
for the changes to propagate.
If you wish you could write one another script to tell onchange to watch for changes to your YAML tokens.
Wrapping up #
So that’s pretty much it. How to take a simple set of YAML design tokens, convert them into JSON so they can be consumed inside Eleventy templates, then generate SCSS to use to style your website.
Since writing this, Heydon published Easily Use Design Tokens In Eleventy which is well worth reading if you’re looking for a simple dependency free approach to a very similar problem.
Haydon’s article shows you how to turn JSON tokens into CSS by using Nunjucks templating and setting a permalink in your theme.njk
template to theme.css
. It’s very elegant and you could easily adapted it to convert YAML to JSON.
Haydon’s approach may well be a better way to go about all this, but I’ll leave that for you to decide.
You can find all the code from this article over on Github.com