Use design tokens to customise Bootstrap

I think design tokens can make for a nice authoring experience for Bootstrap variables. In this article I explain why, and walk through the setup required.

If you are creating a customised version of Bootstrap, and especially if you intend to use it for more than one website / app, I think design tokens can make for a nice authoring experience, as well as bring a bit of future-proofing along for free. This is the approach I took with a recent project; if you just want to see what I did, skip down to .. What I did. First, a little about how I arrived at this method.

I was working on a project which involved updating the visual identity of two websites, which share that visual identity. The two codebases were pretty disparate, though they were both built on Bootstrap. A benefit of using a framework such as Bootstrap is that you can create a custom version then not only use it to build one site, but reuse it to build other sites with the same visual identity.

There are various approaches to sharing a customised Bootstrap between sites. The worst case scenario (which was the case here) is that you rely on those maintaining the sites to make sure that they are visually consistent. Achievable but inefficient. Another approach I've seen is to compile your bespoke Bootstrap then share that code between sites, probably via a CDN. There is value in the CDN here, but more often than not I think this will lead to you having to override your shared Bootstrap styles with site specific ones. This is where frameworks start creating more problems than they solve, in my opinion - causing bloated and overly complicated code. An approach I have favoured in the past is to just maintain a version of the Bootstrap SCSS variables file which can be included into each site's main SCSS file, along with a freshly NPM installed Bootstrap. This is a clean, simple, and flexible approach, but there is room for improvement. If you have taken this approach, you have potentially created a single source of truth for the brand's styles. Nice. The problem is that this single source of truth is heavily tied into Bootstrap. Even if you have taken the time make your variables.scss more versatile with additional, less bootstrapy variables; you are still tied into SCSS at this point. The danger here is that if/when you build something without SASS, not only is your single source of truth going to be of limited use, you are probably going to stop caring about maintaining it.

Design tokens #

This is where design tokens come in. Design tokens simply offer an agnostic way to store variables. That is to say, those variables can in theory be used in any project, regardless languages involved. A clear use-case for design tokens is to share variables (or tokens) between a web project and a native mobile project. It might sound like over-engineering to use design tokens just to generate Bootstrap variables, but the additional setup is small, and to me the workflow felt good.

What I did #

I used Style Dictionary to transform my tokens into SASS variables. I created a new package.json (npm init) for my design tokens project, and added Style Dictionary as a dev dependancy:

npm install -D style-dictionary

Style Dictionary has a command to create a starter project, which you might find useful. Here though I will detail the project structure I used for this particular use-case. The first thing you need in your project is a config.json. In this file you tell Style Dictionary what exactly you want outputted (and to where). Here's what you need for our purposes:

{
"source": ["properties/**/*.json"],
"platforms": {
"scss": {
"transformGroup": "scss",
"buildPath": "build/scss/",
"files": [{
"destination": "_variables.scss",
"format": "scss/variables"
}]
}
}
}

This tells Style Dictionary to look in the properties folder (which we haven't made yet), convert the properties there to SCSS variables, in a file named _variables.scss, at the path build/scss. Tweak this as you please.

Now for those properties. My preference here was to first create non-Bootstrap specific variables, then reference these in my Bootstrap variables. I like having a versatile and consistently named collection of variables (not limited by the constraints of Bootstrap), to use for any additional styles that need to be written. Style Dictionary comes with a suggestion about about how you structure your properties, and thus, what your variables will be named. You do not need to follow this suggestion, but doing so gives you access to some helpers. For example you can convert colour values from one format to another. I found the suggested structure to be sensible, and pretty close to what I would have naturally adopted for my non-Bootstrap variables. So I ran with it, without looking too much into the perks. The suggested property structure is Category / Type / Item. A SASS variable following this structure would look like $size-font-small, or $color-blue-base. size and color being the categories, font and blue being types, and small and base being items. If this is confusing or throws up questions, I suggest you either check out the docs, or just put it to the back of your mind. You don't really need to understand this structure to get going; I think it's worth mentioning though, so you know how I decided to structure my properties. With that said, let's create those properties. My properties folder looked something like this:

|-- bootstrap
|   |-- color.json
|   |-- headings.json
|   |-- link.json
|-- size
|   |-- font.json
|   |-- spacing.json
|-- color.json
|-- font.json

The properties are stored in multiple files, and split into subdirectories for ease of maintenance (this is another reason why I like this design token approach). I don't believe there is any reason you couldn't store all your properties in a single JSON file, if you wanted to. Each JSON file follows the same format. Let's look at color.json as an example:

{
"color": {
"gray": {
"100": { "value": "#F6F6F6" },
"500": { "value": "#939598" },
"900": { "value": "#231f20" }
},
"blue": {
"base": { "value": "#00D1FF" }
},
"text": {
"base": { "value": "{color.gray.900.value}" },
"decoration": { "value": "{color.blue.base.value}" }
}
}
}

Any object in the JSON that has a value attribute is a property. So color.gray.100 is a property, as is color.blue.base. In terms of SASS variables, these will output as:

$color-gray-100: #f6f6f6;
$color-blue-base: #00d1ff;

You can see in the above example that you can reference other properties; so $color-text-base will have the same value as $color-gray-900. The size directory can be thought of as a size.json file split into two smaller files. Both the JSON files within that directory have size as the top level object.

{
"size": {
"font": {
"sm": { "value": "0.875" },
"base": { "value": "1" },
"lg": { "value": "1.125" }
}
}
}
{
"size": {
"spacing": {
"sm": { "value": "0.5" },
"base": { "value": "1" },
"lg": { "value": "1.5" }
}
}
}

Note that these size properties do not include units (px, rem etc). Because I am using the size category, units get added by Style Dictionary. For SCSS, the default is rem.

For my Bootstrap properties, I stopped thinking about the Category / Type / Item structure and focused on generating the required variable names (I found bootstrapvars.com to be a useful reference here). You can organise these properties how you like, I mostly named the JSON files after the first word in the variable name. For example headings.json could contain properties for $headings-font-family, $headings-font-weight, $headings-line-height etc. Because I had already created a comprehensive set of variables, no new values were included in these Bootstrap properties - they were all aliases of existing properties. This may not be the case for you, and if you are only interested in creating Bootstrap specific variables, you could skip straight to this part and include actual values here.

{
"headings": {
"font": {
"family": {
"value": "{font.family.base.value}",
"comment": "Bootstrap"
},
"weight": {
"value": "{font.weight.black.value}",
"comment": "Bootstrap"
}
}
}
}

I used Style Dictionary's comment feature to add a comment to each Bootstrap property, so it could be recognised as such in the outputted _variables.scss file.

Once you have your properties defined, you can generate your SCSS file with:

npx style-dictionary build

That will generate the file at the path specified in your config. That file is now ready to be included in your Bootstrap builds. There are a few ways you could get it into your project. I included the whole design tokens repo as a Git subtree, so I could continue to build the variables from within the Bootstrap project, and push the changes back. I had my task runner watch the design tokens directory, and run the build command from there.

What is primarily achieved by following this method is the ability to easily build your variables into a different format, CSS custom properties for example. Even without taking advantage of that though, I found the structure described above, and the JSON format made for a nice way to structure and keep track of the large number of variables that a customised Bootstrap tends to require.