Automating Multi-Brand × Dark Mode Design Tokens in a Single Pipeline with Tokens Studio — A Complete Guide to Style Dictionary and permutateThemes
Prerequisites: You'll get up to speed faster if you have experience installing the Tokens Studio plugin in Figma, or already understand the basic concepts of Style Dictionary (token JSON → CSS transformation). Estimated reading time: approximately 15 minutes.
If you've ever run a design system, you've likely hit a familiar pain point: maintaining both light and dark modes while also covering style variants across multiple brands. It starts with a handful of CSS files, but before long your folder is packed with files like button-light-brandA.css and button-dark-brandB.css, and a single design change cascades into dozens of manual updates. This happens because two independent dimensions — "mode" and "brand" — become entangled, causing the file structure to explode multiplicatively (N×M).
By the end of this article, you'll never need to manually touch a CSS file just because a new brand is added or dark mode is requested. The key is designing design tokens as a layered set of Sets, then using Themes to automate combinations — generating N×M CSS files simultaneously from a single build pipeline. This is achievable by combining the Sets & Themes features of Tokens Studio (the Figma plugin and standalone platform) with the permutateThemes function from the @tokens-studio/sd-transforms package. We'll walk through each step: Set hierarchy design, $themes.json configuration, the Style Dictionary build script, runtime theme switching, and CI/CD integration.
Core Concepts
What is Tokens Studio?
Tokens Studio is a Figma plugin and standalone web platform (Studio) that lets you centrally manage design tokens as JSON files, sync them to a repository like GitHub, and export them to code. Style Dictionary is an open-source build engine that takes those token JSONs and transforms them into CSS variables, iOS Swift, Android XML, and other platform-specific code. sd-transforms is the official plugin package that converts the Tokens Studio JSON format into something Style Dictionary can understand.
Token Sets — A 3-Tier Token Structure
A Token Set is a logical grouping of tokens, corresponding to a single JSON file. For managing multi-dimensional themes, it's recommended to design them across three tiers.
| Tier | Role | Example |
|---|---|---|
| Global/Core | Raw values such as palettes and base numbers | color.blue.500 = #3B82F6 |
| Semantic/Alias | References with meaningful roles | color.background.primary = {color.blue.500} |
| Component | Component-level scope | button.bg = {color.background.primary} |
Token Reference: Use the curly-brace
{}syntax to reference other tokens. Writing{color.blue.500}means that when the raw value changes, all semantic tokens referencing it are automatically updated.
A practical Set folder structure might look like this:
sets/
├── global.json # Palette, typography raw values (always source)
├── semantic.json # Role alias tokens (always source)
├── mode/
│ ├── light.json # Light mode semantic overrides
│ └── dark.json # Dark mode semantic overrides
└── brand/
├── brandA.json # BrandA-specific colors/fonts
└── brandB.json # BrandB-specific colors/fontsglobal and semantic are always activated as reference value sources (source), while mode/* and brand/* are selectively activated (enabled) depending on the theme, overriding semantic values.
Themes — Automating Multi-Dimensional Combinations
A Theme defines "which Set combination to apply together." You create multiple Theme Groups (dimensions), each containing Theme Options (values), to form a multi-dimensional matrix.
Theme Group: mode → Options: light, dark
Theme Group: brand → Options: brandA, brandBWhen these two groups intersect, four combinations are automatically generated: light_brandA, light_brandB, dark_brandA, dark_brandB. The permutateThemes function internally computes this Cartesian product (all possible cross-combinations of the two groups) and returns it. With 3 modes × 4 brands, 12 combinations are automatically built without any pipeline changes.
Multi-Dimensional Theming: Independent Theme Groups are cross-combined so that values from each dimension are applied independently.
The structure of the $themes.json file that Tokens Studio auto-generates looks like this:
[
{
"id": "light", "name": "light", "group": "mode",
"selectedTokenSets": {
"global": "source", // Always merged (supplies reference values, not directly included in output)
"semantic": "source", // Always merged (supplies reference values, not directly included in output)
"mode/light": "enabled" // Activated only in this theme (included in actual output)
}
},
{
"id": "dark", "name": "dark", "group": "mode",
"selectedTokenSets": {
"global": "source",
"semantic": "source",
"mode/dark": "enabled"
}
},
{
"id": "brandA", "name": "brandA", "group": "brand",
"selectedTokenSets": { "brand/brandA": "enabled" }
},
{
"id": "brandB", "name": "brandB", "group": "brand",
"selectedTokenSets": { "brand/brandB": "enabled" }
}
]Sets marked as source supply reference values but are not directly included in the output, while Sets marked as enabled are included in the actual output for that theme and perform overrides. A disabled state also exists — Sets in this state are included in neither references nor output.
Practical Application
Configuring the Build Pipeline — Building All Combination CSS Files Simultaneously with sd-transforms
The permutateThemes function from the @tokens-studio/sd-transforms package reads $themes.json and generates all possible cross-combinations of Theme Groups.
Node.js Version Note: The
assert { type: 'json' }import assertion in the code below works reliably on Node.js 22 and above. If you're using an older version, it's recommended to usefs.readFileSync+JSON.parseinstead.
// build-tokens.ts (Node.js 22+)
import StyleDictionary from 'style-dictionary';
import { permutateThemes, registerTransforms } from '@tokens-studio/sd-transforms';
import themes from './$themes.json' assert { type: 'json' };
await registerTransforms(StyleDictionary);
// permutateThemes return shape: { light_brandA: [...sets], light_brandB: [...sets], ... }
// Example array element: ['global', 'semantic', 'mode/light', 'brand/brandA']
// The order of the tokenSets array determines merge priority — later files override earlier ones
const permutations = permutateThemes(themes, { separator: '_' });
for (const [themeName, tokenSets] of Object.entries(permutations)) {
const sd = new StyleDictionary({
// Merged in tokenSets array order — files later in the array overwrite identical keys from earlier files
source: tokenSets.map(s => `tokens/${s}.json`),
platforms: {
css: {
// tokens-studio transform group: auto-converts Figma rgba() → CSS hex, handles px units, etc.
transformGroup: 'tokens-studio',
prefix: 'ts',
buildPath: 'dist/',
files: [
{
destination: `${themeName}.css`,
format: 'css/variables',
},
],
},
},
});
await sd.buildAllPlatforms();
}| Code Point | Role |
|---|---|
registerTransforms |
Registers Tokens Studio-specific transform rules with Style Dictionary |
permutateThemes |
Enumerates the Cartesian product combinations across Theme Groups and returns them as an object |
tokenSets array order |
Determines merge priority — files later in the array override earlier ones |
transformGroup: 'tokens-studio' |
Applies Tokens Studio-specific transforms such as Figma rgba() → CSS hex conversion and px unit handling |
It's convenient to register the build script in package.json like this:
{
"scripts": {
"build:tokens": "node --loader ts-node/esm build-tokens.ts"
}
}The build output will produce four files: dist/light_brandA.css, dist/light_brandB.css, dist/dark_brandA.css, and dist/dark_brandB.css. If another brand is added, simply add a Set file and register a Theme Option in $themes.json — the build files will grow automatically.
Implementing Runtime Theme Switching
Runtime theme switching can be implemented by dynamically swapping the built CSS files in the browser.
FOUC (Flash of Unstyled Content): The brief flicker that occurs before styles are applied. Placing the
<link>tag at the very top of<head>prevents this.
<!-- Initial load: reference the default theme CSS via a link tag -->
<link id="theme-css" rel="stylesheet" href="/tokens/light_brandA.css" />type Mode = 'light' | 'dark';
type Brand = 'brandA' | 'brandB';
function switchTheme(mode: Mode, brand: Brand): void {
const link = document.getElementById('theme-css') as HTMLLinkElement | null;
if (!link) return; // Guard for SSR environments or when the element isn't present at script execution time
link.setAttribute('href', `/tokens/${mode}_${brand}.css`);
}
// Example usage: switch to dark mode
switchTheme('dark', 'brandA');This approach is simple to implement, and since all CSS variables are replaced at once, themes are instantly reflected without any changes to component code.
CI/CD Automation — Auto-Build on Token Changes with GitHub Actions
Since Tokens Studio syncs directly with a GitHub repository, you can set up a pipeline where a designer modifying tokens automatically creates a PR, and a merge triggers a build.
# .github/workflows/build-tokens.yml
name: Build Design Tokens
on:
push:
paths:
- 'tokens/**'
- '$themes.json'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- name: Install dependencies
run: pnpm install
- name: Build all theme permutations
run: pnpm build:tokens
- name: Upload CSS artifacts
uses: actions/upload-artifact@v4
with:
name: theme-css
path: dist/*.cssThe workflow only runs when a change is detected in the tokens/** path, reducing unnecessary builds while ensuring token changes are automatically reflected in the code.
Pros and Cons
Advantages
| Item | Details |
|---|---|
| Single source of truth | Figma designs and code share the same token JSON, eliminating design-development mismatches |
| Automatic combination generation | permutateThemes builds N×M combinations without any manual work |
| Platform-agnostic | Supports a wide range of output formats including CSS variables, iOS Swift, Android XML, and Tailwind |
| Git sync | Direct integration with GitHub/GitLab/Bitbucket for PR-based token change history management |
| Scalability | Adding a brand or mode requires only one new Set — the pipeline needs no changes |
Disadvantages and Caveats
| Item | Details | Mitigation |
|---|---|---|
| Pro license required | The Themes feature is exclusive to the paid plan of the Figma plugin | For open-source projects, $themes.json can also be written manually |
| Figma Variable mode count limit | The number of Modes for Figma Variables is limited to 1 on the Free plan and 4 on the Professional plan (separate from the number of Tokens Studio Themes) | Prioritize key combinations or use the standalone Studio platform |
| Learning curve | There is an initial learning cost to understand Set hierarchy design and the $themes.json structure |
It's recommended to first explore the structure using the official Community File (Figma) |
| Growing build file count | More combinations means the number of CSS files grows exponentially | Consider using a CDN caching strategy or switching to a CSS @layer approach |
| JSON conflict potential | Multiple designers editing simultaneously can cause tokens.json merge conflicts |
It's recommended to split Set files by owner and introduce a PR review process |
Override: Redefining a token from a higher-tier Set in a lower-tier Set using the same name. Because Style Dictionary works by having later files in the
sourcearray overwrite earlier ones, the order of thetokenSetsarray directly determines override priority.
Pitfalls and Solutions — The Most Common Mistakes in Practice
-
Mixing semantic tokens into the Global Set: Global should only hold raw palette values, and Semantic should only hold role references — this keeps layer overrides predictable. When the two tiers are mixed, unexpected values can surface in certain modes.
-
Using different token names across Sets: If
mode/light.jsonhascolor.surface.defaultandmode/dark.jsonhascolor.surface.base, the override won't work and both values will coexist. It's critical to maintain consistent token naming conventions across all Sets. -
Missing the
sourcevsenableddistinction in$themes.json:sourcesupplies reference values (used only in computation, not directly included in output), whileenabledmeans the values are included in actual output. Global and Semantic should be set tosource, and override Sets should be set toenabledto generate correct CSS. Reversing this will cause override values to either be missing from the output or included twice.
Closing Thoughts
By designing Tokens Studio's Sets and Themes in 3 tiers and automating the build pipeline with permutateThemes, you can run a fully automated design system where CSS for every brand and mode combination is automatically generated and deployed the moment a designer modifies a token.
Three steps you can take right now:
- Install the Tokens Studio plugin and explore the Community File: After installing the Tokens Studio plugin in Figma, duplicate the Multi-Dimensional Themes Community File to directly examine the actual structure of
global.json,semantic.json,mode/*.json, andbrand/*.json. You can also set up GitHub Sync to connect to a repository and see how token JSONs are synced to the repo. - Set up the local build pipeline: Install dependencies with
pnpm add -D @tokens-studio/sd-transforms style-dictionary, then reference the build script above to add thebuild:tokensscript topackage.jsonand verify your first CSS generation by runningpnpm build:tokens. - Connect CI/CD with GitHub Actions: Add the YAML above as
.github/workflows/build-tokens.yml. When a designer saves tokens in Figma, a PR will be automatically created, and merging it will trigger a CSS build — giving you a code-review-based token change history workflow.
Next Article: Integrating Style Dictionary v4's
@layer-based CSS output with Tailwind v4 to unify utility classes and design tokens
References
Official Documentation
- Themes (pro) — Tokens Studio for Figma Official Docs
- Token Sets — Tokens Studio for Figma Official Docs
- Themes that switch — Simple Switch Guide
- Style Dictionary + SD Transforms Integration Guide
- Theme Groups and Theme Options — Studio Platform Docs
Hands-On Resources
- sd-transforms GitHub Repository
- @tokens-studio/sd-transforms npm Package
- Tokens Studio: Multi-Dimensional Themes Community File (Figma)
Further Reading