Always Twisted

A Design Tokens Workflow (part 6)

Layers, referencing tokens in Style Dictionary

  1. Getting Started With Style Dictionary
  2. Outputting to Different Formats with Style Dictionary
  3. Beyond JSON: Exploring File Formats for Design Tokens
  4. Converting Tokens with Style Dictionary
  5. Organising Outputs with Style Dictionary
  6. Layers, referencing tokens in Style Dictionary – You are here
On This Page:

In previous articles, we explored how to create and generate design tokens using Style Dictionary. Now, we’ll build upon that foundation by setting up token layers in Style Dictionary, showing how to reference tokens across layers and generate outputs for better usability.

A brief explanation of Design Tokens layers

Design Token layers can be essential for creating a scalable and maintainable design system. They can help separate raw values from their contextual meanings. In separating tokens into different layers, such as base, semantic, and component tokens, we can better organise their purposes and usage while ensuring the system remains flexible and future-proof.

The most common layers used for Design Tokens in a Design System include base, semantic, and component tokens, each serve a unique role in your workflow.

Separating tokens into layers provides flexibility and scalability. Semantic tokens allow users to work with meaningful names while base tokens remain stable and reusable. This separation can also support theming, where adjusting base tokens can create design variations without changes to semantic tokens.

While “base” and “semantic” are widely used terms, some teams prefer alternatives like “literal” or “raw” for base tokens and “contextual” for semantic tokens.

Regardless of your chosen terminology, maintaining clarity and consistency within your system is key.

For more details on Design Tokens layers you can read Naming Tokens in Design Systems from Nathan Curtis and Best Practices For Naming Design Tokens, Components And Variables from Cosima on Smashing Magazine

Setting Up Style Dictionary for Token Layers

To effectively separate token layers using Style Dictionary, we will organise our files and folders to reflect the layered structure we are aiming for. This setup allows us to maintain a clear distinction between the raw values and their contextual meanings while also enabling outputs that can meet various design and development goals.

Configuring Style Dictionary to Separate Base and Semantic Tokens

The first step to setting up token layers is to organise the token files into distinct folders for base and semantic tokens. This should make it easier to manage each layer independently.

Here’s an example of a simple folder structure:

tokens/
├── base/
│ ├── colors.tokens
│ ├── spacing.tokens
└── semantic/
├── colors.tokens
├── padding.tokens

To generate separate outputs for these layers, we’ll configure Style Dictionary to filter tokens by folder and output them to distinct files or directories. Below is an example configuration using Style Dictionary:

Code languagejavascript
import StyleDictionary from 'style-dictionary';
// Configure Style Dictionary
const myStyleDictionary = new StyleDictionary({
source: ['src/tokens/**/*.tokens'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'build/css/',
files: [
{
// Output file for base tokens
destination: 'base/tokens.css',
format: 'css/variables',
// Filter for base tokens
filter: (token) => token.filePath.includes('base'),
},
{
// Output file for semantic tokens
destination: 'semantic/tokens.css',
format: 'css/variables',
// Filter for semantic tokens
filter: (token) => token.filePath.includes('semantic'),
},
],
},
},
});
// Build all platforms
myStyleDictionary.buildAllPlatforms();
console.log('Build completed!');

This configuration will build two separate outputs, one for the base tokens (build/base/tokens.css) and another for the semantic tokens (build/semantic/tokens.css).

Referencing Base Tokens in Semantic Tokens

Design Token layers can build upon other layers by referencing them in their definitions. Style Dictionary makes this simple with its {tokenPath} syntax. For example:

Base Tokens

Code languagejson
{
"color": {
"red": {
"400": {
"$value": "#ff0000",
"$type": "color"
}
}
}
}

Semantic Tokens

Code languagejson
{
"color": {
"warning": {
"$value": "{color.red.400}",
"$type": "color"
}
}
}

Using the configuration above this will resolve the {color-warning} in the semantic tokens to a hardcoded value:

Base Tokens CSS

Code languagecss
:root {
--color-red-400: #ff0000;
}

Semantic Tokens CSS

Code languagecss
:root {
--color-warning: #ff0000;
}

By default, Style Dictionary replaces the {color.red.400} reference with its resolved value (#ff0000). This behaviour is intentional and ensures the output works even if references are not supported in the target environment.

Retaining Variable References in Outputs

If you want the semantic tokens to reference base tokens as CSS custom properties or Sass variables (for example), you need to enable the outputReferences option in your Style Dictionary configuration:

The updated configuration file now looks like this:

Code languagejavascript
import StyleDictionary from 'style-dictionary';
const myStyleDictionary = new StyleDictionary({
source: ['src/tokens/**/*.tokens'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'build/css/',
files: [
{
destination: 'base/tokens.css',
format: 'css/variables',
filter: (token) => token.filePath.includes('base'),
},
{
destination: 'semantic/tokens.css',
format: 'css/variables',
filter: (token) => token.filePath.includes('semantic'),
options: {
outputReferences: true,
},
},
],
},
},
});
myStyleDictionary.buildAllPlatforms();
console.log('Build completed!');

With this updated configuration, the output will retain references:

Base Tokens CSS

Code languagecss
:root {
--color-red-400: #ff0000;
}

Semantic Tokens CSS

Code languagecss
:root {
--color-warning: var(--color-red-400);
}

Variable References vs. Hardcoded Values

When generating semantic tokens, you can decide whether to reference base tokens as variables or use hard coded values:

Variable References (--warning-color: var(--color-red-400); )

Referencing base tokens as variables ensures that updates to base tokens cascade automatically to semantic tokens. This is particularly useful for theming or maintaining consistency across large systems. However, it requires including both base and semantic tokens in the final output, which can increase file size and dependencies.

Hardcoded Values (--warning-color: #ff0000)

Hardcoding values in semantic tokens improves portability, as semantic tokens can be used independently of base tokens. However, it sacrifices flexibility, requiring updates in multiple places if a base value changes.

Choosing between these approaches depends on the goals of your design system. Variable references are ideal for systems where flexibility and theming are priorities, while hardcoded values can work better in simpler setups or when portability is key.

Generating Separate Files with Layered Outputs

To effectively manage layered Design Tokens, I think it's essential to generate separate output files that maintain the folder structure and filenames of the source tokens. This approach ensures that each file remains modular and easy to integrate into your workflows. Using Style Dictionary, we can achieve this while maintaining proper references in semantic tokens.

To combine the Design Token Layers with separate file outputs from the fifth article we need to change our configuration file a bit.

The code for this part of the series can be found here

First, we're going to make use of the glob npm package to easily find all the .tokens files in the folder.

Code languagebash
npm install glob

We then need to create an array that processes each .tokens file to generate corresponding .css outputs, preserving the folder structure, filtering tokens by file, and enabling references for semantic tokens.

If you prefer hardcoded values and want to reduce the number of files, simply omit the outputReferences option from the configuration.

Code languagejavascript
import { globSync } from 'glob';
import path from 'path';
import StyleDictionary from 'style-dictionary';
const tokenFiles = globSync('src/tokens/**/*.tokens');
const myStyleDictionary = new StyleDictionary({
source: tokenFiles,
platforms: {
css: {
transformGroup: 'css',
buildPath: 'build/css/',
files: tokenFiles.map((file) => {
const relativeFilePath = path.relative('src/tokens', file).replace(/\\/g, '/');
return {
destination: relativeFilePath.replace('.tokens', '.css'),
format: 'css/variables',
filter: (token) => token.filePath.endsWith(relativeFilePath),
options: {
outputReferences: file.includes('semantic'),
},
};
}),
},
},
});
myStyleDictionary.buildAllPlatforms();
console.log('Build completed!');

This configuration generates .css files for each .tokens file found in the src/tokens directory, preserving the folder structure and applying specific logic for base and semantic tokens:

Directory Structure

For a src/tokens/ directory structured like this:

src/tokens/
├── base/
│ ├── color.tokens
│ ├── spacing.tokens
├── semantic/
│ ├── color.tokens
│ ├── typography.tokens

The output directory will look like this:

build/css/
├── base/
│ ├── color.css
│ ├── spacing.css
├── semantic/
│ ├── color.css
│ ├── typography.css

Base Token Output

Tokens in base files output hard coded values without references. For example:

tokens/base/color.tokens

Code languagejson
{
"color": {
"red": {
"400": {
"$value": "#ff0000",
"$type": "color"
}
}
}
}

build/base/color.css

Code languagecss
:root {
--color-red-400: #ff0000;
}

Semantic Token Output

Tokens in semantic files reference base tokens using var(...). For example: tokens/semantic/color.tokens

Code languagejson
{
"color": {
"warning": {
"$value": "{color.red.400}",
"$type": "color"
}
}
}

build/semantic/color.css

Code languagecss
:root {
--color-warning: var(--color-red-400);
}

A Note on Warnings

Using this approach will likely have Style Dictionary in a bit of a spin and provide a warning in the terminal when the code is generated.

Code languagebash
⚠️ build/css/semantic/padding.css
While building semantic/padding.css, filtered out token references were found; output may be unexpected. Ignore this warning if intentional.

The warning indicates that some tokens in the output reference other tokens that were filtered out, potentially leading to broken references in the generated file. This happens because outputReferences: true tries to preserve variable references (var(--token-name)), but the referenced tokens are not included in the same output file.

I think you can safely ignore the warning if you’ve confirmed that the referenced tokens are included in other output files used alongside the semantic files, and the output meets your expectations.

However, if the semantic files need to function independently, you should either address the warning by including all necessary references in the same file or remove outputReferences: true to default to hardcoded values.

Component Tokens

While this article focuses on two layers of Design Tokens, it’s worth briefly introducing component tokens as the next layer in a Design Token hierarchy. Component tokens are values tailored to specific elements and components, such as buttons, cards, or headings, and are often built upon semantic tokens to ensure consistency with the broader Design System.

As an example, a button's background colour might use a semantic token like --primary-color (if you set outputPreferences: true like above):

Code languagecss
--button-primary-bg: var(--primary-color);

Component tokens can provide flexibility for customisation while maintaining alignment with the Design System’s semantics. They enable teams to define granular, component-specific styles without duplicating design intent across multiple components.

This approach also makes it easier to manage component-level theming and variations. For instance, a --button-secondary-bg token could reference the same semantic base but apply a slightly different tint for differentiation.

We’ll explore the role of component tokens, their setup, and practical applications in a future article, where they’ll be integrated as part of a cohesive Design Tokens workflow.

Layering Design Tokens with Style Dictionary is a practical way to create a clear and scalable Design System. By splitting tokens into layers like base and semantic, you can define them more clearly, adapt easily for theming, and make life simpler for design and development teams.

Struggling to decide how to name your design tokens consistently across your system?

I can guide you in creating a naming convention that’s intuitive and consistent.

get in touch!