Since the release of the second editor’s draft of the design token
specification in 2022 and the call for tool makers to start implementing
and providing feedback, the landscape of design token tools has evolved
rapidly. Tools like code generators, documentation systems, and UI design
software are now better equipped to support design tokens, underscoring
their growing importance in modern UI architecture.
In this article, I'll explain what design tokens are, when they are useful
and how to apply them effectively. We'll focus on key architectural
decisions that are often difficult to change later, including:
-
How to organize design tokens in layers to balance scalability,
maintainability and developer experience.
-
Whether all tokens should be made available to product teams or just a
subset.
-
How to automate the distribution process of tokens across teams.
Role of design tokens
Around 2017, I was involved in a large project that used the Micro
Frontend Architecture to scale development teams. In this setup,
different teams were responsible for different parts of the user
interface, which could be even on the same page. Each team could deploy
its micro-frontend independently.
There were various cases where components would be displayed on top of
each other (such as dialogs or toasts appearing on top of content
areas), which were not part of the same micro frontend. Teams used the
CSS property z-index to control the stacking order, often relying on
magic numbers—arbitrary values that weren’t documented or standardized.
This approach did not scale as the project grew. It led to issues that
took effort to fix, as cross-team collaboration was needed.
The issue was eventually addressed with design tokens and I think makes
a good example to introduce the concept. The respective token file might
have looked similar to this:
{
"z-index": {
"$type": "number",
"default": {
"$value": 1
},
"sticky": {
"$value": 100
},
"navigation": {
"$value": 200
},
"spinner": {
"$value": 300
},
"toast": {
"$value": 400
},
"modal": {
"$value": 500
}
}
}
The design tokens above represent the set of z-index values that can be
used in the application and the name gives developers a good idea of
where to use them. A token file like this can be integrated into the
designers’ workflow and also be used to generate code, in a format that
each team requires. For example, in this case, the token file might have
been used to generate CSS or SCSS variables:
css
:root {
--z-index-default: 1;
--z-index-sticky: 100;
--z-index-navigation: 200;
--z-index-spinner: 300;
--z-index-toast: 400;
--z-index-modal: 500;
}
scss
$z-index-default: 1;
$z-index-sticky: 100;
$z-index-navigation: 200;
$z-index-spinner: 300;
$z-index-toast: 400;
$z-index-modal: 500;
What are design tokens?
Salesforce originally introduced design tokens to streamline design
updates to multiple platforms.
The Design Tokens Community Group describes design tokens as “a
methodology for expressing design decisions in a platform-agnostic way
so that they can be shared across different disciplines, tools, and
technologies
Let’s break this down:
Cross-Disciplinary Collaboration: Design tokens act as a common language
that aligns designers, developers, product managers, and other
disciplines. By offering a single source of truth for design decisions,
they ensure that everyone involved in the product life cycle is on the
same page, leading to more efficient workflows.
Tool integration: Design tokens can be integrated into various design
and development tools, including UI design software, token editors,
translation tools (code generators), and documentation systems. This
enables design updates to be quickly reflected in the code base and are
synchronized across teams.
Technology adaptability: Design tokens can be translated into different
technologies like CSS, SASS, and JavaScript for the web, and even used
on native platforms like Android and iOS. This flexibility enables
design consistency across a variety of platforms and devices.
Establishing a single source of truth
A key benefit of design tokens is their ability to serve as a single
source of truth for both design and engineering teams. This ensures that
multiple products or services maintain visual and functional
consistency.
A translation tool takes one or more design token files as input and
generates platform-specific code as output. Some translation tools can
also produce documentation for the design tokens in the form of HTML. At
the time of writing, popular translation tools include Style
Dictionary, Theo, Diez or Specify App.
Figure 1: Translation tool
Automated design token distribution
In this section, we’ll explore how to automate the distribution of
design tokens to product teams.
Let’s assume our goal is to provide teams with updated, tech-specific
design tokens immediately after a designer makes a change. To achieve
this, we can automate the translation and distribution process using a
deployment pipeline for design tokens. Besides platform-specific code
artifacts (like CSS for the web, XML for Android etc.), the pipeline
might also deploy the documentation for the design tokens.
One crucial requirement is keeping design tokens under version control.
Thankfully, plugins for popular design tools like Figma already
integrate with Git providers like GitHub. It's considered best practice
to use the Git repository as the single source of truth for design
tokens—not the design tool itself. However, this requires the plugin to
support syncing both ways between the repository and the design tool,
which not all plugins do. As of now, Tokens Studio is a plugin that
offers this bidirectional syncing. For detailed guidance on integrating
Tokens Studio with different Git providers, please refer to their
documentation. The tool enables you to configure a target branch and
supports a trunk-based as well as a pull-request-based workflow.
Once the tokens are under version control, we can set up a deployment
pipeline to build and deploy the artifacts needed by the product teams,
which include platform-specific source code and documentation. The
source code is typically packaged as a library and distributed via an
artifact registry. This approach gives product teams control over the
upgrade cycle. They can adopt updated styles by simply updating their
dependencies. These updates may also be applied indirectly through
updates of component libraries that use the token-based styles.
Figure 2: Automated design token distribution
This overall setup has allowed teams at Thoughtworks to roll out smaller
design changes across multiple front-ends and teams in a single day.
Fully automated pipeline
The most straightforward way to design the pipeline would be a fully
automated trunk-based workflow. In this setup, all changes pushed to the
main branch will be immediately deployed as long as they pass the
automated quality gates.
Such a pipeline might consist of the following jobs:
Check: Validate the design token files using a design token validator or
a JSON validator.
-
Build: Use a translation tool like Style Dictionary to convert design
token files into platform-specific formats. This job might also build
the docs using the translation tool or by integrating a dedicated
documentation tool.
-
Test: This job is highly dependent on the testing strategy. Although
some tests can be done using the design token file directly (like
checking the color contrast), a common approach is to test the
generated code using a documentation tool such as Storybook. Storybook
has excellent test support for visual regression tests, accessibility
tests, interaction tests, and other test types.
-
Publish: Publish updated tokens to a package manager (for example,
npm). The release process and versioning can be fully automated with a
package publishing tool that is based on Conventional Commits like
semantic-release. semantic-release also allows the deployment of
packages to multiple platforms. The publish job might also deploy
documentation for the design tokens.
-
Notify: Inform teams of the new token version via email or chat, so
that they can update their dependencies.

Figure 3: Fully automated deployment pipeline
Pipeline including manual approval
Sometimes fully automated quality gates are not sufficient. If a manual
review is required before publishing, a common approach is to deploy an
updated version of the documentation with the latest design token to a
preview environment (a temporary environment).
If a tool like Storybook is used, this preview might contain not only
the design tokens but also show them integrated with the components used
in the application.
An approval process can be implemented via a pull-request workflow. Or,
it can be a manual approval / deployment step in the pipeline.
Figure 4: Deployment pipeline with manual approval
Organizing tokens in layers
As discussed earlier, design tokens represent design decisions as data.
However, not all decisions operate at the same level of detail. Instead,
ideally, general design decisions guide more specific ones. Organizing
tokens (or design decisions) into layers allows designers to make
decisions at the right level of abstraction, supporting consistency and
scalability.
For instance, making individual color choices for every new component
isn’t practical. Instead, it’s more efficient to define a foundational
color palette and then decide how and where those colors are applied.
This approach reduces the number of decisions while maintaining a
consistent look and feel.
There are three key types of design decisions for which design tokens
are used. They build on top of one another:
- What design options are available to use?
- How are those styles applied across the user interface?
- Where exactly are those styles applied (in which components)?
There are various names for these three types of tokens (as usual,
naming is the hard part). In this article, we’ll use the terms proposed
by Samantha Gordashko: option tokens, decision tokens and component
tokens.
Let’s use our color example to illustrate how design tokens can answer
the three questions above.
Option tokens: defining what design options are provided
Option tokens (also called primitive tokens, base tokens, core tokens,
foundation tokens or reference tokens) define what styles can be used in
the application. They define things like color palettes, spacing/sizing
scales or font families. Not all of them are necessarily used in the
application, but they present reasonable options.
Using our example, let’s assume we have a color palette with 9 shades
for each color, ranging from very light to highly saturated. Below, we
define the blue tones and grey tones as option-tokens:
{
"color": {
"$type": "color",
"options": {
"blue-100": {"$value": "#e0f2ff"},
"blue-200": {"$value": "#cae8ff"},
"blue-300": {"$value": "#b5deff"},
"blue-400": {"$value": "#96cefd"},
"blue-500": {"$value": "#78bbfa"},
"blue-600": {"$value": "#59a7f6"},
"blue-700": {"$value": "#3892f3"},
"blue-800": {"$value": "#147af3"},
"blue-900": {"$value": "#0265dc"},
"grey-100": {"$value": "#f8f8f8"},
"grey-200": {"$value": "#e6e6e6"},
"grey-300": {"$value": "#d5d5d5"},
"grey-400": {"$value": "#b1b1b1"},
"grey-500": {"$value": "#909090"},
"grey-600": {"$value": "#6d6d6d"},
"grey-700": {"$value": "#464646"},
"grey-800": {"$value": "#222222"},
"grey-900": {"$value": "#000000"},
"white": {"$value": "#ffffff"}
}
}
}
Although it’s highly useful to have reasonable options, option tokens
fall short of being sufficient for guiding developers on how and where
to apply them.
Decision tokens: defining how styles are applied
Decision tokens (also called semantic tokens or system tokens) specify
how those style options should be applied contextually across the UI.
In the context of our color example, they might include decisions like
the following:
- grey-100 is used as a surface color.
- grey-200 is used for the background of disabled elements.
- grey-400 is used for the text of disabled elements.
- grey-900 is used as a default color for text.
- blue-900 is used as an accent color.
- white is used for text on accent color backgrounds.
The corresponding decision token file would look like this:
{
"color": {
"$type": "color",
"decisions": {
"surface": {
"$value": "{color.options.grey-100}",
"description": "Used as a surface color."
},
"background-disabled": {
"$value": "{color.options.grey-200}",
"description":"Used for the background of disabled elements."
},
"text-disabled": {
"$value": "{color.options.grey-400}",
"description": "Used for the text of disabled elements."
},
"text": {
"$value": "{color.options.grey-900}",
"description": "Used as default text color."
},
"accent": {
"$value": "{color.options.blue-900}",
"description": "Used as an accent color."
},
"text-on-accent": {
"$value": "{color.options.white}",
"description": "Used for text on accent color backgrounds."
}
}
}
}
As a developer, I would mostly be interested in the decisions, not the
options. For example, color tokens typically contain a long list of
options (a color palette), while very few of those options are actually
used in the application. The tokens that are actually relevant when
deciding which styles to apply, would be usually the decision tokens.
Decision tokens use references to the option tokens. I think of
organizing tokens this way as a layered architecture. In other articles,
I have often seen the term tier being used, but I think layer is the
better word, as there is no physical separation implied. The diagram
below visualizes the two layers we talked about so far:
Figure 5: 2-layer pattern
Component tokens: defining where styles are applied
Component tokens (or component-specific tokens) map the decision tokens
to specific parts of the UI. They show where styles are applied.
The term component in the context of design tokens does not always map
to the technical term component. For example, a button might be
implemented as a UI component in some applications, while other
applications just use the button HTML element instead. Component tokens
could be used in both cases.
Component tokens can be organised in a group referencing multiple
decision tokens. In our example, this references might include text- and
background-colors for different variants of the button (primary,
secondary) as well as disabled buttons. They might also include
references to tokens of other types (spacing/sizing, borders etc.) which
I'll omit in the following example:
{
"button": {
"primary": {
"background": {
"$value": "{color.decisions.accent}"
},
"text": {
"$value": "{color.decisions.text-on-accent}"
}
},
"secondary": {
"background": {
"$value": "{color.decisions.surface}"
},
"text": {
"$value": "{color.decisions.text}"
}
},
"background-disabled": {
"$value": "{color.decisions.background-disabled}"
},
"text-disabled": {
"$value": "{color.decisions.text-disabled}"
}
}
}
To some degree, component tokens are simply the result of applying
decisions to specific components. However, as this example shows, this
process isn’t always straightforward—especially for developers without
design experience. While decision tokens can offer a general sense of
which styles to use in a given context, component tokens provide
additional clarity.
Figure 6: 3-layer pattern
Note: there may be “snowflake” situations where layers are skipped. For
example, it might not be possible to define a general decision for every
single component token, or those decisions might not have been made yet
(for example at the beginning of a project).
How many layers shall I use?
Two or three layers are quite common amongst the bigger design systems.
However, even a single layer of design tokens already greatly limits the
day-to-day decisions that need to be made. For example, just deciding
what units to use for spacing and sizing became a somewhat nontrivial
task with up to 43 units for length implemented in some browsers (if I
counted correctly).
A three-layer architecture should offer the best developer experience.
However, it also increases maintenance effort and token count, as new
tokens are introduced with each new component. This can result in a
larger code base and heavier package size.
Starting with two layers (option and decision tokens) can be a good idea
for projects where the major design decisions are already in place
and/or relatively stable. A third layer can still be added if there is a
clear need.
An additional component layer makes it easier for designers to change
decisions later or let them evolve over time. This flexibility could be
a driving force for a three-layer architecture. In some cases, it might
even make sense to start with component tokens and to add the other
layers later on.
Ultimately, the number of layers depends on your project's needs and how
much flexibility and scalability are required.
Token scope
I already mentioned that while option tokens are very helpful to
designers, they might not be relevant for application developers using
the platform-specific code artifacts. Application developers will
typically be more interested in the decision/component tokens.
Although token scope is not yet included in the design token spec, some
design systems already separate tokens into private (also called
internal) and public (also called global) tokens. For example, the
Salesforce Lightning Design System introduced a flag for each token.
There are various reasons why this can be a good idea:
- it guides developers on which tokens to use
- fewer options provide a better developer experience
- it reduces the file size as not all tokens need to be included
-
private/internal tokens can be changed or removed without breaking
changes
A downside of making option tokens private is that developers would rely
on designers to always make those styles available as decision or
component tokens. This could become an issue in case of limited
availability of the designers or if not all decisions are available, for
example at the start of a project.
Unfortunately, there is no standardized solution yet for implementing
scope for design tokens. So the approach depends on the tool-chain of
the project and will most likely need some custom code.
File-based scope
Using Style Dictionary, we can use a filter to expose only public
tokens. The most straightforward approach would be to filter on the file
ending. If we use different file endings for component, decision and
option tokens, we can use a filter on the file path, for example, to
make the option tokens layer private.
Style Dictionary config
const styleDictionary = new StyleDictionary({
"source": ["color.options.json", "color.decisions.json"],
"platforms": {
"css": {
"transformGroup": "css",
"files": [
{
"destination": "variables.css",
"filter": token => !token.filePath.endsWith('options.json'),
"format": "css/variables"
}
]
}
}
});
The resulting CSS variables would contain only these decision tokens,
and not the option tokens.
Generated CSS variables
:root {
--color-decisions-surface: #f8f8f8;
--color-decisions-background-disabled: #e6e6e6;
--color-decisions-text-disabled: #b1b1b1;
--color-decisions-text: #000000;
--color-decisions-accent: #0265dc;
--color-decisions-text-on-accent: #ffffff;
}
A more flexible approach
If more flexibility is needed, it might be preferable to add a scope
flag to each token and to filter based on this flag:
Style Dictionary config
const styleDictionary = new StyleDictionary({
"source": ["color.options.json", "color.decisions.json"],
"platforms": {
"css": {
"transformGroup": "css",
"files": [
{
"destination": "variables.css",
"filter": {
"public": true
},
"format": "css/variables"
}
]
}
}
});
If we then add the flag to the decision tokens, the resulting CSS would
be the same as above:
Tokens with scope flag
{
"color": {
"$type": "color",
"decisions": {
"surface": {
"$value": "{color.options.grey-100}",
"description": "Used as a surface color.",
"public": true
},
"background-disabled": {
"$value": "{color.options.grey-200}",
"description":"Used for the background of disabled elements.",
"public": true
},
"text-disabled": {
"$value": "{color.options.grey-400}",
"description": "Used for the text of disabled elements.",
"public": true
},
"text": {
"$value": "{color.options.grey-900}",
"description": "Used as default text color.",
"public": true
},
"accent": {
"$value": "{color.options.blue-900}",
"description": "Used as an accent color.",
"public": true
},
"text-on-accent": {
"$value": "{color.options.white}",
"description": "Used for text on accent color backgrounds.",
"public": true
}
}
}
}
Generated CSS variables
:root {
--color-decisions-surface: #f8f8f8;
--color-decisions-background-disabled: #e6e6e6;
--color-decisions-text-disabled: #b1b1b1;
--color-decisions-text: #000000;
--color-decisions-accent: #0265dc;
--color-decisions-text-on-accent: #ffffff;
}
Such flags can now also be set through the Figma UI (if using Figma
variables as a source of truth for design tokens). It is available as
hiddenFromPublishing flag via the Plugins API.
Should I use design tokens?
Design tokens offer significant benefits for modern UI architecture, but
they may not be the right fit for every project.
Benefits include:
- Improved lead time for design changes
-
Consistent design language and UI architecture across platforms and
technologies
-
Design tokens being relatively lightweight from an implementation
point of view
Drawbacks include:
- Initial effort for automation
- Designers might have to (to some degree) interact with Git
- Standardization is still in progress
Consider the following when deciding whether to adopt design tokens:
When to use design tokens
-
Multi-Platform or Multi-Application Environments: When working across
multiple platforms (web, iOS, Android…) or maintaining several
applications or frontends, design tokens ensure a consistent design
language across all of them.
-
Frequent Design Changes: For environments with regular design updates,
design tokens provide a structured way to manage and propagate changes
efficiently.
-
Large Teams: For teams with many designers and developers, design
tokens facilitate collaboration.
-
Automated Workflows: If you’re familiar with CI/CD pipelines, the
effort to add a design token pipeline is relatively low. There are
also commercial offerings.
When design tokens might not be necessary
-
Small projects: For smaller projects with limited scope and minimal
design complexity, the overhead of managing design tokens might not be
worth the effort.
-
No issue with design changes: If the speed of design changes,
consistency and collaboration between design and engineering are not
an issue, then you might also not need design tokens.
Acknowledgments
Thanks to Berni Ruoff—I don't think I would have written this article
without all the great discussions we had about design systems and design
tokens (and for giving feedback on the first draft). Thanks to Shawn
Lukas, Jeen Suratriyanont, Mansab Uppal and of course Martin for all the
feedback on the subsequent drafts.