Image for post
Image for post

Design Tokens beyond colors, typography, and spacing.

Cristiano Rastelli
Nov 12, 2019 · 14 min read

In recent months, I couldn’t help but notice how many people (and companies) when referring to “design tokens” what they actually mean is colors, typography, spacing (and in some cases elevation, timing, and sizing).

While this is perfectly true, I think a key part of the picture is missing: design tokens can be much more than this.

As Sarah Federman puts it perfectly:

In this post, I share my point of view and my experience on this topic, showing why so much more can be done with them, and how.

“Design tokens are so hot right now!” has become a meme in the Design Systems community, so you probably already know what they are. But if you don’t, or want to know more, I suggest you check out Sönke Rohde’s article (on how Salesforce, before everyone else, introduced the idea of design tokens), watch Jina Anne’s online course and talk, Danny Banksblog post, Stuart Robson’s repository, or Robin Rendle’s article. There is also a brand new W3C Working Group if you fancy contributing.

For an overview of how we use them in our company (MagicLab), check out this post of mine:

Design tokens for colors, typography and spacing

As I said above, many people think of design tokens as “a set of core design properties of an application or website”. While this is pretty intuitive it can also be quite misleading and reductive.

Take a look at this playground, and see how the only things you can tweak are colors and typography:

Try this management tool for design tokens, and see how it’s built around the idea of “core” values of design properties:

Image for post
Image for post
A screenshot of the interface used to add a new design token.

Look at the many existing plugins for Figma, Sketch or Framer, that exist for exporting design tokens: they’re all based on the idea of exporting granular and isolated values for colors, font-sizes/line-heights, spacing, etc.

Open the “design tokens” page of the many design systems available online, and in most cases what you see are colors, typography, spacing, elevation, timing, sizing, etc.

Let me be clear: there is nothing wrong with this.

But, as I said before, I think design tokens can do much more than this.

In my experience, design tokens’ great potential and power are fully expressed when two other things form part of the equation, when they are:

  1. used to express properties and values for components;
  2. annotated with meta-information.

Design tokens for components

There’s no reasons why design tokens can’t be used to describe a component’s properties.

From a purely technical point of view, design tokens are organised lists of key-value pairs that describe design decisions. So it’s just a matter of convenience (and context) what are the boundaries that we assume when describing a UI via design tokens.

In some cases, especially when a design system is in its early stages, limiting ourselves to just core values (eg. colors, typography, spacing) makes perfect sense. But once the system becomes more complex, and starts to include more and more components, there are a lot of good reasons for using design tokens for these as well.

Moreover, basic colors and typography don’t change very frequently (re-branding exercises are a case in point). But by contrast, in my experience, components are constantly evolving.

In recent months, many new components have been added to our design system; some existing ones have been fine-tuned (and in some cases even refactored); a few others have been extended for use in different contexts or visual states. By contrast, only a couple of colors and one font size have been changed.

Whenever a component is created, we discuss — with the designers and developers involved in the design system — the possible states and variants of the component, and its specific design decisions. Then we translate all these specifications into specific design tokens.

Image for post
Image for post
Example of a set of design tokens for a component (“LifestyleBadge”)

Here, for example, is what the JSON file for the LifestyleBadge component looks like (we use Style Dictionary to manage our design tokens):

{
"lifestylebadge": {
"height": {
"value": "34",
"type": "size"
},
"border_radius": {
"value": "17",
"type": "size"
},
"padding": {
"start": {
"value": "{spacing.md.value}",
"type": "size"
},
"end": {
"value": "{spacing.md.value}",
"type": "size"
}
},
"icon_size": {
"value": "{icon.size.md.value}",
"type": "size"
},
"spacing_icon_text": {
"value": "{spacing.xsm.value}",
"type": "size"
},
"base": {
"text_color": {
"value": "{color.gray_dark.value}",
"type": "color"
},
"background_color": {
"value": "{color.gray_light.value}",
"type": "color"
}
},
"selected": {
"text_color": {
"value": "{color.white.value}",
"type": "color"
},
"background_color": {
"value": "{color.primary.value}",
"type": "color"
}
}
}
}

These values are processed in different output formats, and then distributed to multiple platforms (mobile web, iOS, Android) and products (we currently support four main brands plus a few white-labels).

Once the tokens for a component are added to the system, the developers that assume the task of implementing it will immediately have at hand all the necessary design specifications:

Image for post
Image for post
The “LifestyleBadge” tokens for Android, as visible in our design system

At first sight, this may look like a case of over-engineering, adding a lot of complexity to the system. But actually, what we’ve found is that this allows us to distribute information to the “consumers” (the developers) in a reliable, well-established way. This in turns reduces opportunities for confusion, misunderstandings and (human) error.

The developers themselves have started demanding specific tokens whenever a component is added to the system. This is because it obviates the need for them to open a Sketch file (or a Sympli page), and then figure out all the sizes, colors, and component specs. Instead, all they have to do is update the version of the design tokens package in their codebase, launch a sync/update script, and all these specs are automatically there for them, abstracted in easy-to-read variable names. (Plus, we use illustrations like the one below, to show how/where a component’s design tokens are defined and used).

Image for post
Image for post
Example of a set of design tokens for another component (“ActionSheet”). Even the alignment along the vertical axis alignment (“gravity”) can be expressed as a design token!

Another important aspect of this approach is that you derive the same benefits as when the design tokens are used for core design properties:

  • In a multi-product design system like ours it makes easy to achieve multi-product components, where the same component (with exactly the same code) has a different look and feel for different products/brands. Doing this helps us keep the cost of scaling down (eg. when adding new applications to our portfolio)!
  • Whenever a designer decides to update the visual styling of a component (eg. the background color) or its layout/geometry (eg. the horizontal padding) all that these changes require is an update of a few values in a single JSON file, and the change of a version number for the imported tokens in the codebase, and nothing else: the cost of change is extremely low!

If we see design tokens as a way to convey information, then it makes absolute sense to use them for components as well.

Design tokens meta-information

Both of the main management tools for design tokens (Theo & Style Dictionary) support the addition of meta-data to the key-value pairs. While they can be used for adding comments and annotations, their real power manifests when they’re used to add an extra layer of meaning.

For example, you can add type information to the values, for use later on, when they are processed to generate specific outputs. You can add grouping information which can be used later for organising or filtering them in very specific ways. Many other kinds of information can be added, depending on context and needs. The key here is to add what has meaning for you, your context/usage/need.

The first meta-information that we introduced, once we had adopted the design tokens in our design system, was “documentation/comment”. This was used to enable the addiction of extra information to a token, that would appear in the design system website. We decided to use the documentation namespace for it, rather than just using a comment property, in case we needed to add even more information (eg. if a design token is deprecated, and what it needs to be replaced with).

Here, for example, is how we add a comment to a design token:

{
"actionsheet": {

"item": {
"height": {
"value": "48",
"type": "size",
"documentation": {
"comment": "Notice: this is the 'internal' size, the border will be added as extra"
}
},

}
}
}

And this is rendered in the documentation of the design token in this way:

Image for post
Image for post

The second piece of meta-information that we added immediately after was “type”. This is not strictly a type in the “programming-language” sense, but rather in a a semantic way.

Below some examples of types we’re using:

{
"color": {
"primary": {
"value": "{color.palette.purple_grape.value}",
"type": "color"
}
},
"icon": {
"size": {
"xsm": {
"value": "10",
"type": "size"
}
}
},
"tooltip": {
"shadow": {
"opacity": {
"value": "0.08",
"type": "opacity"
}
},
"animation": {
"timing_bounce": {
"value": "0.9",
"type": "time",
"unit": "s"
}
}
},
"chat": {
"bubble": {
"relative_width": {
"value": "0.8",
"type": "ratio"
}
}
},
"actionsheet": {
"gravity": {
"value": "center",
"type": "string"
}
}
}

The “type” meta-data is used in the post-processing script to create custom output for different platforms. Below you can see a piece of code relating to the template used to generate the XML file for Android:

Image for post
Image for post
For Android, depending on the token “type”, we generate values with specific formatting.

And this is the custom formatted output:

Image for post
Image for post
The XML generated for Android, with the XML format/type depending on the token “type” meta value.

More recently, we have added a third kind of meta-data to our set of design tokens: “group” information. This is one of the most important ones, and it’s used for multiple reasons.

Filtering

For some products/targets, we only want to release a subset of tokens (eg. just the colors). In this case, it was quite easy for us to add a custom filter to Style Dictionary like this:

Image for post
Image for post
A custom script to filter only “proper” colors.

If you notice, we are applying two filters here: one for the “color” token “type”, and one to filter out any “aliases”. This is because we make use of special colors (like color-palette-purple-grave) as pointers to hex color values, but because we don’t need them as generated tokens, we flag them with a meta-data called isAlias.

This filter is then used to generate color-only specific files:

Image for post
Image for post
We filter the design tokens based on their “type” meta-data, to generate color-only files in output.

This is just one example of how you can use meta-data to filter tokens and generate specific outputs. I can imagine many other instances where this could be used, depending on your context and needs.

Grouping

Another way in which we’re using the meta-data of design tokens is for grouping them in specific ways.

For example, using the “group” attribute we can create lists of tokens that share the same group, and later use these lists (or maps) in our codebase.

Assigning a different “group” to the design tokens for color values, icons sizes, like here:

// color/xxx.json
{
"color": {
"primary": {
"value": "{color.palette.purple_grape.value}",
"type": "color",
"group": "brand"
},

"generic_red": {
"value": "{color.palette.pink_salmon.value}",
"type": "color",
"group": "generic"
},

"gray_dark": {
"value": "#767676",
"type": "color",
"group": "mono"
},
"feature": {
"verification": {
"value": "{color.palette.blue_neon.value}",
"type": "color",
"group": "features"
},

},
"provider": {
facebook": {
"value": "#4867aa",
"type": "color",
"group": "providers"
},

},
"others": {
error": {
"value": "{color.generic_red.value}",
"type": "color",
"group": "others"
},

}
}
}
// icon.json
{
"icon": {
"size": {
"xsm": {
"value": "10",
"type": "size",
"group": "size"
},
"sm": {
"value": "16",
"type": "size",
"group": "size"
},
"md": {
"value": "22",
"type": "size",
"group": "size"
},
"lg": {
"value": "30",
"type": "size",
"group": "size"
},
"xlg": {
"value": "36",
"type": "size",
"group": "size"
},
"xxlg": {
"value": "46",
"type": "size",
"group": "size"
}
},
"jumbo-size": {
"sm": {
"value": "{brick.size.sm.value}",
"type": "size",
"group": "size"
},
"md": {
"value": "{brick.size.md.value}",
"type": "size",
"group": "size"
},
"lg": {
"value": "{brick.size.lg.value}",
"type": "size",
"group": "size"
}
}
}
}
// spacing.json
{
"spacing": {
"xsm": {
"value": "4",
"type": "size",
"group": "size"
},
"sm": {
"value": "8",
"type": "size",
"group": "size"
},
"md": {
"value": "12",
"type": "size",
"group": "size"
},
"lg": {
"value": "16",
"type": "size",
"group": "size"
},
"xlg": {
"value": "24",
"type": "size",
"group": "size"
},
"xxlg": {
"value": "32",
"type": "size",
"group": "size"
},
"gap": {
"value": "{spacing.lg.value}",
"type": "size",
"group": "size"
}
}
}

we can generate Sass maps like this one:

$tokens-color-map: (
providers: (
provider-facebook: $token-color-provider-facebook,

),
mono: (
black: $token-color-black,
gray-dark: $token-color-gray-dark,
gray: $token-color-gray,
gray-light: $token-color-gray-light,
white: $token-color-white
),
others: (
error: $token-color-error,

),
brand: (
primary: $token-color-primary,

),
generic: (
generic-red: $token-color-generic-red,

),
features: (

feature-verification: $token-color-feature-verification
),
);
$tokens-icon-map: (
size: (
size-xsm: $token-icon-size-xsm,
size-sm: $token-icon-size-sm,
size-md: $token-icon-size-md,
size-lg: $token-icon-size-lg,
size-xlg: $token-icon-size-xlg,
size-xxlg: $token-icon-size-xxlg,
jumbo-size-sm: $token-icon-jumbo-size-sm,
jumbo-size-md: $token-icon-jumbo-size-md,
jumbo-size-lg: $token-icon-jumbo-size-lg
),
);$tokens-spacing-map: (
size: (
xsm: $token-spacing-xsm,
sm: $token-spacing-sm,
md: $token-spacing-md,
lg: $token-spacing-lg,
xlg: $token-spacing-xlg,
xxlg: $token-spacing-xxlg,
gap: $token-spacing-gap
),
);
...

These maps now allow us to write declarations such as these:

Image for post
Image for post
Image for post
Image for post
How we’re using Sass maps generated by the design tokens in our codebase.

In this way we’re not merely abstracting away the declaration of lists of classes/properties based on color/size/etc. More importantly for us, different products can have different lists of colors, or different spacing scales, or icon sizes, etc. but the same Sass code, now works for any list of colors, spacing, sizing, etc.

These Sass files are now universal, and can be used for multi-product and multi-platform components!

Whenever a color is added, or removed, the list automatically gets updated. No code change is required at either component or application level. Everything is controlled by the design tokens declarations.

And this is not only for CSS/Sass. The same benefit of having “lists of tokens associated by the same group” can also be applied to JavaScript code:

Image for post
Image for post
In the design system website, we showcase all the possible colors of a component using the lists of tokens generated via the “group” meta-data associated with them.

Which leads to another possible application, for CSS-in-JS. In this case, the list of possible colors (or spaces, or sizes) for a component, simply becomes a loop/map on a list of key-value objects, directly generated by the design tokens tooling.

Following the same approach, we have also recently used the “group” property to generate TypeScript definitions. Using a custom template in Style Dictionary we have been able to generate an output file like this:

declare namespace Tokens {namespace Color {
enum Providers {
PROVIDER_FACEBOOK = 'provider-facebook',

}
enum Mono {
BLACK = 'black',
GRAY_DARK = 'gray-dark',
GRAY = 'gray',
GRAY_LIGHT = 'gray-light',
WHITE = 'white'
}
enum Others {
ERROR = 'error',

}
enum Brand {
PRIMARY = 'primary',

}
enum Generic {
GENERIC_RED = 'generic-red',

}
enum Features {

FEATURE_VERIFICATION = 'feature-verification'
}
type Color = Providers | Mono | Others | Brand | Generic | Features;}namespace Icon {
enum Size {
XSM = 'xsm',
SM = 'sm',
MD = 'md',
LG = 'lg',
XLG = 'xlg',
XXLG = 'xxlg',
JUMBO_SM = 'jumbo-sm',
JUMBO_MD = 'jumbo-md',
JUMBO_LG = 'jumbo-lg'
}
}
namespace Spacing {
enum Size {
XSM = 'xsm',
SM = 'sm',
MD = 'md',
LG = 'lg',
XLG = 'xlg',
XXLG = 'xxlg',
GAP = 'gap'
}
}
}export default Tokens;

These TypeScript definitions and enums can then be consumed directly by the client codebases, to ensure strict type safety (and autocomplete in the IDEs).

One last possible use of meta-data to add extra semantic meaning to design tokens, especially relationships, is for example to declare text styles. We all know that to have a minimum viable text style, you need at least a font-family, a font-size, a line-height, and a font-weight. How can you define the relationship between distinct design tokens, and explicitly declare their relationship, to create an “H1 text style”? One can imagine using the naming (so all the properties of the same style have the same prefix/suffix). Why then not use some meta-data associated with the tokens, to identify them and filter/group them at process time? This is not something we needed to implement, but if we had I would have certainly used a meta-data attribute associated with the different style values.

Conclusions

The key takeaways of this post are the following:

  • Design tokens are not only a way to store information, but also a way to convey information.
  • Design tokens are useful for describing core design values, but their full power emerges when used to describe the specs of UI components
  • There are many ways in which adding meta-data to the design tokens adds extra meaning to them. This meaning can be processed to determine and convey even more information, beyond the mere key/value definition.

For example, using such meta-data it is possible to:

  • annotate a design token with useful information
  • selectively generate output according to specific filters
  • generate dynamic lists driven by tokens for Sass, JavaScript, TypeScript
  • making use of these dynamic lists, use the same component (with the same code) for multiple products/platforms

I have tried to show real use cases of how we are using this approach to design tokens in our design system. But this is how we use them, to satisfy our needs.

I’m sure many of you will have (or find) dozens and dozens of other examples of how design tokens can be used, beyond colors, typography, and spacing.

It’s a matter of imagining. Think about what you can do, when you add meta-information to the tokens, that has special meaning for you, and in the context of how and where these tokens are used.

And having done so, do please share them with the community :)

In the meantime, Sarah Federman has published the slides of her recent talk at WebDirections about “Customizability in Design Systems”. In her talk Sarah shows how far they have pushed their design tokens’ architecture, to support Atlassian’s design system theming and customizability. And the slide “Future possibilities” is simply mind blowing. Brava Sarah! 🙌

Bumble Tech

This is the Bumble tech team blog focused on technology and…

Thanks to Jan Toman and carl crawford

Cristiano Rastelli

Written by

Passionate web designer & developer. In love with Atomic Design, Design Systems & CSS architecture. Attendee/speaker/organiser of tech conferences and meetups.

Bumble Tech

We’re the tech team behind social networking apps Bumble and Badoo. Our products help millions of people build meaningful connections around the world.

Cristiano Rastelli

Written by

Passionate web designer & developer. In love with Atomic Design, Design Systems & CSS architecture. Attendee/speaker/organiser of tech conferences and meetups.

Bumble Tech

We’re the tech team behind social networking apps Bumble and Badoo. Our products help millions of people build meaningful connections around the world.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

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