Building a Web · iOS · Android Design Token Pipeline at Once with Style Dictionary v4 + W3C DTCG
Any team that has operated a design system has likely encountered this situation at least once. A designer changes a single brand color, the web team, iOS team, and Android team each update their own files separately, someone misses it, and slightly different colors end up deployed on each platform. For teams without a design system, this problem repeats every sprint; for teams already operating one, they hit the same wall when expanding to multi-brand or dark mode. The fundamental solution to this inefficiency is Design Tokens and the build pipeline that automates them.
By leveraging Style Dictionary v4, driven by the open-source community, and the W3C DTCG (Design Tokens Community Group) standard format, you can automatically generate CSS variables, Swift constants, and Android XML resources simultaneously from a single JSON source. This approach is already supported by more than 10 tools including Figma, Penpot, and Sketch, and has been adopted by enterprise design systems such as IBM Carbon, Atlassian, and SAP Fiori.
After reading this article, you will be able to define tokens in DTCG format and build a pipeline that automatically generates output for three platforms from a single Style Dictionary v4 configuration file.
Core Concepts
What Are Design Tokens
Design tokens represent design decisions — such as color, typography, spacing, and shadow — as platform-independent key-value pairs. For example, a single token defined as color-primary: #0055FF is transformed into a CSS custom property (--color-primary) on the web, a Swift constant on iOS, and an XML color resource on Android.
Single Source of Truth: A structure where tokens are defined in one place and consumed by all platforms. When a value changes, everything is updated simultaneously.
Tokens are typically divided into three layers:
| Layer | Name | Example | Role |
|---|---|---|---|
| 1 | Primitive | blue-500: #0055FF |
Raw values, palette definition |
| 2 | Semantic | color-primary: {blue-500} |
Assigns meaning, theme switching point |
| 3 | Component | button-bg: {color-primary} |
Component-specific mapping |
By following this layered structure, switching to dark mode only requires changing the references in the Semantic layer, allowing theme changes without touching any component code.
W3C DTCG Format: Why Standards Matter
When every team defines design tokens differently, migration costs arise each time tools change. To solve this problem, the W3C Design Tokens Community Group created a JSON-based exchange standard, officially announcing Design Tokens Format Module 2025.10 as its first stable version on October 28, 2025.
The core of the DTCG format is the $ prefix key:
{
"color": {
"brand": {
"primary": {
"$value": "#0055FF",
"$type": "color",
"$description": "브랜드 주 색상"
}
}
},
"size": {
"spacing": {
"sm": { "$value": "8px", "$type": "dimension" },
"md": { "$value": "16px", "$type": "dimension" }
}
}
}The role of
$type: Style Dictionary v4 uses this field to automatically handle unit conversions per platform. For thedimensiontype, it convertspxtoptfor iOS output.
Use .tokens.json as the file extension, and reference tokens can point to other tokens using the form "$value": "{color.brand.primary}". This reference mechanism is the core of the theme system.
Key Changes in Style Dictionary v4
Style Dictionary is the tool that connects the DTCG format to an actual build pipeline. Initially developed by Amazon and now community-driven, this open-source build system underwent architectural-level changes in v4.
| Change | Before v3 | v4 |
|---|---|---|
| Module system | CommonJS | Full ES Module transition |
| Token type resolution | Depends on CTI directory structure | Uses $type property |
| DTCG support | Unofficial | Official native support |
| TypeScript | Partial support | Independent type interfaces provided |
| Runtime environment | Node.js only | Can also run in browser |
CTI (Category/Type/Item): The directory-based token classification scheme used until Style Dictionary v3. Token types were implicitly determined by path structures like
color/brand/primary. Starting from v4, the$typefield explicitly replaces this, giving you the freedom to organize files however you like.
In addition to the built-in Transform groups (css, ios-swift, android), you can register custom Transforms directly, allowing flexible extension for special output formats.
Practical Application
Example 1: Basic Cross-Platform Pipeline Setup
Let's start with the project structure:
design-tokens/
├── tokens/
│ ├── color.tokens.json
│ ├── spacing.tokens.json
│ └── typography.tokens.json
├── config.mjs
├── package.json
└── dist/
├── web/variables.css
├── ios/StyleDictionary.swift
└── android/res/values/colors.xmlToken definition (tokens/color.tokens.json)
{
"color": {
"brand": {
"primary": { "$value": "#0055FF", "$type": "color" },
"secondary": { "$value": "#FF6B00", "$type": "color" }
},
"neutral": {
"100": { "$value": "#F5F5F5", "$type": "color" },
"900": { "$value": "#1A1A1A", "$type": "color" }
}
}
}Style Dictionary v4 configuration (config.mjs)
In v4, the top-level source applies globally. If you need to use different sources per platform, it is recommended to use separate config files or leverage StyleDictionary.extend().
export default {
source: ['tokens/**/*.tokens.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'dist/web/',
files: [{
destination: 'variables.css',
format: 'css/variables'
}]
},
ios: {
transformGroup: 'ios-swift',
buildPath: 'dist/ios/',
files: [{
destination: 'StyleDictionary.swift',
format: 'ios-swift/class.swift'
}]
},
android: {
transformGroup: 'android',
buildPath: 'dist/android/res/values/',
files: [
{ destination: 'colors.xml', format: 'android/colors' },
{ destination: 'dimens.xml', format: 'android/dimens' }
]
}
}
};Registering package.json scripts
For a pnpm project, registering the build command in scripts is good for consistency.
{
"scripts": {
"build": "style-dictionary build --config config.mjs"
},
"devDependencies": {
"style-dictionary": "^4.0.0"
}
}Afterwards, install with pnpm add -D style-dictionary and build with pnpm build.
Comparing output by platform
| Platform | Output format | Naming convention | Value format |
|---|---|---|---|
| Web CSS | --color-brand-primary |
kebab-case | HEX #0055FF |
| iOS Swift | colorBrandPrimary |
camelCase | UIColor(red:green:blue:alpha:) |
| Android XML | color_brand_primary |
snake_case | ARGB #FF0055FF |
Android ARGB format: Android XML color representation uses
#AARRGGBBorder. In#FF0055FF, the leadingFFrepresents alpha (100% opacity). This differs from the web's#RRGGBBAAorder, so readers without Android development experience should take note.
/* dist/web/variables.css */
:root {
--color-brand-primary: #0055FF;
--color-brand-secondary: #FF6B00;
}// dist/ios/StyleDictionary.swift
// iOS 팀에서 출력 타입 검토를 권장합니다. SwiftUI 기반 프로젝트라면 Color 타입으로의 출력도 고려해보시면 좋습니다.
public class StyleDictionary {
public static let colorBrandPrimary = UIColor(
red: 0.00, green: 0.33, blue: 1.00, alpha: 1
)
public static let colorBrandSecondary = UIColor(
red: 1.00, green: 0.42, blue: 0.00, alpha: 1
)
}<!-- dist/android/res/values/colors.xml -->
<!-- Android 팀에서 ARGB 포맷 및 리소스 네이밍 검토를 권장합니다 -->
<resources>
<color name="color_brand_primary">#FF0055FF</color>
<color name="color_brand_secondary">#FFFF6B00</color>
</resources>Example 2: Dark Mode Theming with Reference Tokens
Using reference tokens in the Semantic layer greatly simplifies theme switching implementation.
Primitive tokens (tokens/primitive.tokens.json)
{
"palette": {
"blue": {
"500": { "$value": "#0055FF", "$type": "color" },
"600": { "$value": "#0044CC", "$type": "color" }
},
"gray": {
"50": { "$value": "#FAFAFA", "$type": "color" },
"950": { "$value": "#0A0A0A", "$type": "color" }
}
}
}Semantic tokens — light mode (tokens/semantic-light.tokens.json)
{
"color": {
"background": {
"default": { "$value": "{palette.gray.50}", "$type": "color" },
"surface": { "$value": "#FFFFFF", "$type": "color" }
},
"action": {
"primary": { "$value": "{palette.blue.500}", "$type": "color" }
}
}
}Semantic tokens — dark mode (tokens/semantic-dark.tokens.json)
{
"color": {
"background": {
"default": { "$value": "{palette.gray.950}", "$type": "color" },
"surface": { "$value": "#1C1C1C", "$type": "color" }
},
"action": {
"primary": { "$value": "{palette.blue.600}", "$type": "color" }
}
}
}Branching builds by theme in configuration (config.mjs)
Since different sources are needed per platform, this pattern uses StyleDictionary.extend().
import StyleDictionary from 'style-dictionary';
const baseConfig = {
platforms: {
css: {
transformGroup: 'css',
buildPath: 'dist/web/'
}
}
};
const lightSD = new StyleDictionary({
...baseConfig,
source: [
'tokens/primitive.tokens.json',
'tokens/semantic-light.tokens.json'
],
platforms: {
css: {
...baseConfig.platforms.css,
files: [{
destination: 'theme-light.css',
format: 'css/variables',
options: { selector: ':root, [data-theme="light"]' }
}]
}
}
});
const darkSD = new StyleDictionary({
...baseConfig,
source: [
'tokens/primitive.tokens.json',
'tokens/semantic-dark.tokens.json'
],
platforms: {
css: {
...baseConfig.platforms.css,
files: [{
destination: 'theme-dark.css',
format: 'css/variables',
options: { selector: '[data-theme="dark"]' }
}]
}
}
});
await lightSD.buildAllPlatforms();
await darkSD.buildAllPlatforms();Actual output
/* dist/web/theme-light.css */
:root, [data-theme="light"] {
--color-background-default: #FAFAFA;
--color-background-surface: #FFFFFF;
--color-action-primary: #0055FF;
}/* dist/web/theme-dark.css */
[data-theme="dark"] {
--color-background-default: #0A0A0A;
--color-background-surface: #1C1C1C;
--color-action-primary: #0044CC;
}In this structure, no component code needs to be modified when applying dark mode. Simply toggling the data-theme="dark" attribute on the <html> tag automatically swaps the CSS variables.
Example 3: Automated Deployment Pipeline with GitHub Actions
A CI/CD pipeline that automatically builds and publishes to an npm package whenever token files change.
# .github/workflows/tokens.yml
name: Build & Publish Design Tokens
on:
push:
paths:
- 'tokens/**/*.tokens.json'
branches:
- main
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- run: pnpm install
- name: Build tokens
run: pnpm build
- name: Bump version (patch)
run: npm version patch --no-git-tag-version
- name: Publish to npm
run: pnpm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}Version management note: If you don't bump the version before running
pnpm publish, CI will fail due to an already-existing version. The example above uses a simplenpm version patch, but depending on team size, it is recommended to introduce a more systematic version management tool such as changesets or semantic-release.
Each platform team can add the npm package as a dependency, and updating the package version immediately applies the new tokens.
Pros and Cons Analysis
Advantages
| Item | Details |
|---|---|
| Single Source of Truth | A single color change is automatically reflected across web, iOS, and Android — inconsistencies eliminated at the source |
| Multi-brand & theming | Light/dark mode and brand variants managed via reference overrides without file duplication |
| Eliminates manual handoff | Figma update → GitHub sync → automatic code generation minimizes friction between designers and developers |
| Minimal vendor lock-in | Based on the W3C DTCG 2025.10 stable spec — not locked into any specific tool |
| Unlimited extensibility | Custom Transforms, Formats, and Preprocessors handle any platform |
| Type safety | TypeScript support catches token typos and type errors at build time |
Disadvantages and Caveats
| Item | Details | Mitigation |
|---|---|---|
| Initial setup complexity | Significant upfront investment needed for per-platform Transform groups and naming convention design | Recommended to design the Primitive → Semantic → Component 3-layer structure first |
| Naming strategy failure | A poorly structured token naming system makes maintenance harder, not easier | Document naming guidelines early; prepare automated migration scripts for changes |
| Lack of developer buy-in | Without a collaborative process between designers and developers, actual adoption rates are low | Team-wide onboarding sessions; auto-generate Storybook token visualization docs for visibility |
| Some newer spec features unsupported | Per the current roadmap, some features of the 2025.10 spec may not be supported in v4 — check official docs | Start with v4 and write in DTCG standard format to minimize future migration costs |
| Mobile-native feature limitations | Platform-specific features like iOS Dynamic Color and Android Material You require separate handling | Design a platform-specific override layer in addition to common tokens |
The Most Common Mistakes in Practice
-
Connecting Primitive tokens directly to components: Using raw values like
button-bg: {palette.blue.500}directly means you have to update each component one by one when switching themes. Always maintain a structure that routes through the Semantic layer (color-action-primary). -
Over-engineering tokens: The tendency to turn every style attribute into a token only increases the management burden. It is recommended to apply tokens only to values that need to be shared across platforms or that designers are likely to change.
-
Starting without a naming convention: Bulk-renaming hundreds of token names later is extremely painful. It is strongly recommended that the team agrees on a naming rule in the form of
{category}-{meaning}-{variant}-{state}before starting.
Closing Thoughts
Writing even a single frequently-changed color token in DTCG format is the starting point for this pipeline. Small beginnings accumulate into a structure where platform inconsistencies disappear and designer decisions are automatically translated into code.
Three steps you can take right now:
-
Create a single token file: Write the 5–10 colors that change most frequently in your project into a
tokens/color.tokens.jsonfile using DTCG format ($value,$type). You can test it directly in the browser at Style Dictionary Playground. -
Write config.mjs and run the build: Install with
pnpm add -D style-dictionary, copy the configuration file from the examples above, and runpnpm build— CSS, Swift, and XML files will be automatically generated in thedist/folder. -
Connect GitHub Actions: Add a workflow file to trigger an automatic build when token files change, then connect version management (changesets or semantic-release) with
pnpm publish— this completes a structure where each platform team can receive the latest tokens simply by updating the package version.
Next article: We will cover how to set up an end-to-end pipeline where connecting Tokens Studio with Figma Variables automatically creates a GitHub PR whenever a designer changes a color in Figma.
References
- Style Dictionary Official Docs | styledictionary.com
- Style Dictionary v4 Migration Guide | styledictionary.com
- Style Dictionary DTCG Support Docs | styledictionary.com
- W3C Design Tokens Community Group | w3.org
- Design Tokens Format Module 2025.10 Official Spec | designtokens.org
- DTCG Stable Version Announcement | w3.org
- Tokens Studio Style Dictionary Integration Docs | docs.tokens.studio
- Style Dictionary v4 Release Plan | tokens.studio
- Martin Fowler: Design Token-Based UI Architecture | martinfowler.com
- Style Dictionary GitHub Repository | github.com