Design Tokens: Visual Consistency and Efficiency

Posted by Sabrina Bosco

Introduction

In the world of design, maintaining visual consistency across various platforms and products can be a challenging task. Design tokens offer a solution by providing a unified system for managing design elements and attributes. These tokens, often described as the DNA of design systems, empower teams to efficiently create cohesive user experiences.

What are design tokens?

Design tokens are abstract representations of design properties such as colours, typography, spacing, and other visual elements that can be defined and reused throughout a design system. Design tokens provide a centralized and flexible approach to managing design attributes. They act as a single source of truth for designers, developers, and other stakeholders, enabling consistent design implementation across different platforms.

There is a W3C Design Tokens Community Group that is defining the shape of a Token. We used their Design Tokens Format Module as a source to better shape our design tokens (some examples below).

Key benefits of design tokens

1. Consistency: design tokens establish a shared language and visual consistency by defining standardised values for various design elements. With a set of predefined tokens, designers and developers can ensure that colours, typography, and spacing remain consistent throughout a project. This consistency fosters a coherent user experience and strengthens a brand’s identity.

2. Efficiency: by abstracting design properties into tokens, teams can streamline the design process. Designers can focus on creating high-level concepts and defining the core visual attributes, while developers can reference these tokens directly in their codebase. This separation of concerns allows for quicker prototyping, easier maintenance, and seamless collaboration between designers and developers.

3. Scalability: design tokens are particularly valuable when working on large-scale projects or across multiple platforms. They enable teams to adapt and scale their designs efficiently. With tokens in place, making changes to design attributes becomes much simpler. Altering a single token value can automatically propagate the change across the entire design system, reducing the risk of inconsistencies and saving valuable time and effort.

4. Reusability: design tokens promote reusability by encapsulating design properties. Once a token is defined, it can be reused across different projects, platforms, and even organisations. This reusability fosters a modular approach to design systems, encouraging the sharing of design assets and reducing duplication of work. It also facilitates a more coherent experience for users, as they encounter familiar design patterns across various touchpoints.

Stages of maturity

Watching a talk about Design Systems (A Maturity Model For Design Systems by Ben Callahan), I came across the concept of “stages of maturity”:

Stage 1: everything you do towards your very first release. Idea of what a design system is, define a promise of what the design system will be.

Stage 2: you are live with your first version. Now you need to grow adoption. Few potential consumers. At this stage, the thing you build is never the thing that everybody wants to adopt, is only an early iteration. At this stage, it should be easy for consumers to adopt the system.
We have to start supporting users, satisfy their expectations. The focus will shift when you transition to stage 3. It’s very common to go back and forth between stage 2 and stage 3 - it might take years to iterate over everything until you have a solid foundation.

Stage 3: surviving the challenge. All new set of challenges. You have more consumers, more bugs are found, more feature requests, more people that are trying to do things with your design system that you would never imagine to do. You have to prove that what you promised is giving results.
You need to build an iterative product mindset at this stage, evolving what you already know and have.

Stage 4: another mindset shift. The design system delivered on its promise and is considered influential. Change of the perception of the Design System Team; they are now people who have a voice in the direction of products. Highly evolved engagement from consumers, the system is stable.

At Springer Nature, as I write this post, our Elements Design System is currently at Stage 2.

Implementing design tokens at Springer Nature

Design tokens can be implemented in different ways, depending on the specific needs and tools of a project. We, at Springer Nature, decided to go with the following architecture:

1. JSON files: design tokens can be defined in JSON files, so they are easy to manage and can be version controlled. These files can be imported or exported and can be used by designers and developers in their respective workflows.

Following the design tokens best practices suggested by many in the community, we decided to have 3 tiers of tokens, so the folder structure of our Tokens looks like this:

tokens/literals/brand/literal-type-folder/literal-type-file.json
tokens/alias/brand/alias-composite-type-folder/alias-composite-type-file.json
components/component-name/scss/component-name.tokens.scss

The first tier (or level) is literal tokens, it contains all the raw values, in json format, that hold design decisions. In a system where reusability and consistency has to be the top priority, we can use the literal tokens inside the alias tokens.

An example of a literal token is:

{
  "font-family": {
    "serif": {
      "name": "",
      "value": "'Merriweather', serif",
      "description": "The SN serif font family",
      "type": "family-name",
      "category": "font-family",
      "themeable": false,
      "meta": {
        "public": true,
        "deprecated": false,
        "documented": true,
        "experimental": false,
        "SassVariable": true,
        "CSSCustomProperty": false,
        "UtilityClass": true
      }
    },
    "sans": {
      "name": "",
      "value": "'Merriweather Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;",
      "description": "The SN sans-serif font family",
      "type": "family-name",
      "category": "font-family",
      "themeable": false,
      "meta": {
        "public": true,
        "deprecated": false,
        "documented": true,
        "experimental": false,
        "SassVariable": true,
        "CSSCustomProperty": false,
        "UtilityClass": true
      }
    }
  }
}

The alias tokens are the second level and are meant to give semantic meaning to the value that they hold, which is a literal token.

An example of alias token is:

{
	"typography": {
		"font-family": {
			"default": {
				"name": "typography-font-family-default",
				"value": "{font-family.sans}",
				"description": "Default Font family for SN",
				"category": "typography",
				"type": "font-family",
				"themeable": false,
				"meta": {
					"public": true,
					"deprecated": false,
					"documented": true,
					"experimental": false,
					"SassVariable": true,
					"CSSCustomProperty": false
				}
			},
			"long-text": {
				"name": "typography-font-family-long-text",
				"value": "{font-family.serif}",
				"description": "Font family for SN, long text",
				"category": "typography",
				"type": "font-family",
				"themeable": false,
				"meta": {
					"public": true,
					"deprecated": false,
					"documented": true,
					"experimental": false,
					"SassVariable": true,
					"CSSCustomProperty": false
				}
			}
		},
    "font-size": {
      "s": {
				"name": "typography-font-size-s",
				"value": "{font-size.400}",
				"description": "S Font size for SN",
				"category": "typography",
				"type": "font-size",
				"themeable": false,
				"meta": {
					"public": true,
					"deprecated": false,
					"documented": true,
					"experimental": false,
					"SassVariable": true,
					"CSSCustomProperty": false
				}
			},
			"default": {
				"name": "typography-font-size-default",
				"value": "{font-size.450}",
				"description": "Default Font size for SN",
				"category": "typography",
				"type": "font-size",
				"themeable": false,
				"meta": {
					"public": true,
					"deprecated": false,
					"documented": true,
					"experimental": false,
					"SassVariable": true,
					"CSSCustomProperty": false
				}
			}
    }
  }
}

In this code example of an alias token for typography, we can see how many literal tokens can be used in the same file in order to shape the typography alias token and give to the alias values a semantic meaning. In this case, we took the raw value of font-family.sans and gave a semantic meaning by referencing it in typography.font-family.default.

The third and last tier of tokens is the component level, where we can reference alias tokens or we can decide to assign other values and then use those component tokens inside the component style.

We are directly populating the .token.scss file inside the component with alias tokens that are needed.

Another best practice is to use literal tokens only internally (so within the Design System) and not in the external world (e.g. application-level implementation). In our case, it means that we should use literal tokens only inside the alias tokens and that any design token in a component level style or in an application level style should be an alias and not a literal.

For this reason we recommend our users to have a look at the compiled alias folder (a collection of compiled design tokens to scss variables) and search for a semantic meaning that would cover their needs.

2. Design Token Libraries: We use Style Dictionary to generate and consume design tokens. We created custom scripts that are configured so that our tokens in json format can be used for the web as scss variables. We have one script for each tier.

Here we can see an example of a js script that uses Style Dictionary to process the literal tokens:

const { readdirSync } = require('fs');
const StyleDictionaryPackage = require('style-dictionary');
const _ = require('../node_modules/style-dictionary/lib/utils/es6_');

StyleDictionaryPackage.registerTransform({
	name: 'name/cti/kebab',
	type: 'name',
	transformer: function (token, options) {
		return `${options.prefix}-${_.kebabCase(token.path.join(' '))}`;
	}
});

function getStyleDictionaryConfig(brand, literals) {
	let destination = `themes/${brand}/scss`;

	return {
		source: [
			`${__dirname}/literal/${brand}/**/*.json`,
		],
		platforms: {
			scssVariables: {
				transformGroup: 'web',
				transform: 'name/cti/kebab',
				prefix: 't-l',
				buildPath: `${destination}/tokens/literal/`,
				files: literals.map(literal => {
					return {
						destination: `_${literal}.tokens.scss`,
						format: 'scss/variables',
						filter: (token) => token.filePath.includes(brand) && token.attributes.category === literal, // split tokens in different files based on category
						options: {
							outputReferences: true,
							showFileHeader: false
						}
					};
				})
			}
		}
	};
}

['springernature', 'nature'].map(function (brand) {
	let dir = `${__dirname}/literal/${brand}`;
	const literals = readdirSync(dir);
	const brands = StyleDictionaryPackage.extend(getStyleDictionaryConfig(brand, literals));
	brands.buildAllPlatforms();
});

And here we can see an example of a js script that uses Style Dictionary to process the alias tokens:

const { readdirSync } = require('fs');
const StyleDictionaryPackage = require('style-dictionary');
const _ = require('../node_modules/style-dictionary/lib/utils/es6_');

StyleDictionaryPackage.registerTransform({
	name: 'name/cti/kebab',
	type: 'name',
	transformer: function (token) {
		if (token.isSource) {
			return `t-a-${_.kebabCase(token.path.join(' '))}`;
		}
		return `t-l-${_.kebabCase(token.path.join(' '))}`;
	}
});

function getStyleDictionaryConfig(brand, aliases) {
	let destination = `themes/${brand}/scss`;

	return {
		include: [`${__dirname}/literal/**/*.json`],
		source: [
			`${__dirname}/alias/${brand}/**/*.json`
		],
		platforms: {
			scssVariables: {
				transformGroup: 'web',
				transform: 'name/cti/kebab',
				buildPath: `${destination}/tokens/alias/`,
				files: aliases.map(alias => {
					return {
						destination: `_${alias}.tokens.scss`,
						format: 'scss/variables',
						filter: (token) => token.filePath.includes(brand) && token.attributes.category === alias, // split tokens in different files based on category,
						options: {
							outputReferences: true,
							showFileHeader: false
						}
					};
				})
			}
		}
	};
}

['springernature', 'nature'].map(function (brand) {
	let directory = `${__dirname}/alias/${brand}`;
	const aliases = readdirSync(directory);
	const brands = StyleDictionaryPackage.extend(getStyleDictionaryConfig(brand, aliases));
	brands.buildAllPlatforms();
});

Conclusion

Design tokens provide a powerful mechanism for achieving visual consistency and efficiency within design systems. By centralising design attributes and offering a reusable and scalable approach, design tokens empower teams to create cohesive user experiences across platforms and projects. Embracing design tokens not only saves time and effort but also facilitates collaboration between designers and developers, resulting in easier design and development, and better outcomes for users. Last but not least, we should take into consideration that the landscape of design tokens is, as of today, still very young and many decisions will become conventions with time.

Resources

Amazing list of resources about Design Tokens, with great articles, videos and example projects: Awesome Design Tokens by Stuart Robson


Find this post useful, or want to discuss some of the topics?

About The Authors