Token Compiler Comparison: Choosing a Build Tool
Part of Token Scaling, Validation & CI Pipelines. This page frames the token compiler selection decision as an architectural choice — not a preference — because the tool you commit to determines your output formats, theming model, extensibility ceiling, and migration cost for years.
Choosing the wrong compiler creates compounding debt: custom transforms that fight the tool’s native model, multi-platform output that requires manual post-processing, and a pipeline that breaks every time the upstream tool releases a major version. The goal here is to give you enough signal to make an informed call on day one, and a migration path if you’re already on the wrong tool.
The Decision Surface
Problem Framing
The failure mode that lands teams in compiler debt looks the same everywhere: a developer picks a tool based on a blog post, wires it into CI, and six months later tries to add iOS Swift output or a second brand theme. The tool either can’t do it natively or requires a transform chain complex enough that it would have been faster to switch tools at the start.
A concrete example: Theo’s format model is flat — it expects a single global.yml or global.json and outputs per-format files. Reasonable for one brand, but when a second brand needs a different set of primitive values, Theo has no native concept of multi-set merging. Teams end up writing a pre-processor to generate multiple Theo inputs and a post-processor to merge outputs, which is exactly the kind of glue code that becomes unmaintainable when Theo’s own transforms change.
The DTCG format compounds this. If your design tooling exports W3C DTCG JSON (the $value / $type schema), you need a compiler that understands it natively. Neither Theo nor Style Dictionary v3 parsed DTCG out of the box — only Cobalt and Style Dictionary v4 do. Picking a tool without confirming DTCG support means writing a converter layer before compilation even begins.
Feature Comparison
| Feature | Style Dictionary v4 | Theo (Salesforce) | Cobalt (cobalt-ui) |
|---|---|---|---|
| Format support | JSON, YAML, DTCG (v4), custom parsers | JSON, YAML, SCSS maps | DTCG-native; JSON5, YAML via plugins |
| Transforms | Composable transform chains; 30+ built-ins | Fixed set; limited composability | Plugin-based; typed transform pipeline |
| Multi-platform output | CSS, SCSS, JS, TS, iOS Swift, Android XML, Compose | CSS, SCSS, JS, Sass variables, Common JS | CSS vars, JS/TS, JSON, Tailwind config |
| Theming / multi-brand | Sets + references + token filtering | Manual file splitting | Sets API; first-class theme layering |
| Extensibility | Custom parsers, transforms, formats, actions | Plugin API (limited docs) | Plugin API; TypeScript-typed |
| Maintenance status | Active; Amazon + community; v4 stable | Low-activity; last release 2023 | Active; small team; growing adoption |
| DTCG compliance | v4 full; v3 requires converter | None native | Full; spec-tracking |
| Best for | Enterprise multi-platform, iOS/Android teams | Legacy Salesforce Lightning codebases | CSS-first, modern W3C toolchains |
The DTCG Format
The W3C Design Tokens Community Group specification defines a canonical JSON schema for design tokens. A DTCG file uses $value and $type keys instead of bare value, and supports composite types like typography and shadow as structured objects rather than concatenated strings.
{
"color": {
"action": {
"primary": {
"$type": "color",
"$value": "#2563eb",
"$description": "Primary interactive surface — buttons, links, focus rings"
}
}
}
}
If your Figma Tokens or Tokens Studio export produces this format, you need either Cobalt or Style Dictionary v4. Feeding a DTCG file to Style Dictionary v3 or Theo produces silent mismatches — the $value key is not recognized as the token value, so you get empty or undefined output for every token. This failure is hard to catch without schema validation, because the build completes without errors. Pairing your compiler with JSON Schema Validation for Tokens catches this class of mismatch before compilation runs.
Three-Tier Architectural Trade-Offs
Style Dictionary: ceiling vs. onboarding cost
- High extensibility ceiling; every output format, parser, and transform is replaceable — but the config surface is large and the v3→v4 API break is non-trivial.
- Native iOS and Android output makes it the only viable choice for native mobile teams; competitors require hand-rolling platform formatters.
Theo: simplicity vs. longevity
- Minimal API means a new team member can understand the full pipeline in an afternoon; but the maintenance trajectory means you are likely inheriting a frozen tool.
- YAML support is a genuine advantage for teams that find JSON token files unwieldy; but it does not compensate for lack of DTCG support or theming primitives.
Cobalt: spec alignment vs. ecosystem maturity
- First-class DTCG means zero converter code for modern Figma toolchains; the trade-off is a smaller plugin ecosystem and fewer production references.
- The TypeScript-typed plugin API reduces transform bugs; but a small core team means slower response to breaking changes in upstream DTCG spec drafts.
Evaluation Workflow
Follow this sequence before committing to a compiler. Each step produces a concrete artifact that either confirms the tool or rules it out.
-
Inventory your token sources. List every format currently produced by your design tooling (Figma Tokens, Tokens Studio, Specify, hand-authored JSON). Identify whether the format is DTCG, legacy Theo-style flat JSON, or nested Style Dictionary JSON.
-
Map required output platforms. Enumerate every consumer: CSS custom properties for web, Swift/ObjC constants for iOS, XML resources for Android, Compose tokens for Android Compose, Tailwind config, Sass variables, JavaScript constants for React Native. Any non-web platform routes you toward Style Dictionary.
-
Prototype each candidate tool’s build step. Take a 20-token sample (one color ramp, one spacing scale) and wire it through each candidate. Measure: does it parse your source format without conversion? Does the output match what consumers expect?
-
Stress-test theming. Create two brand sets with conflicting primitive values. Verify the tool can produce two independent CSS output files from a shared token base without manual output merging.
-
Check CI integration. Run the build step in a GitHub Actions runner with
--no-install(using the cache). Verify exit codes are reliable:0on success, non-zero on transform errors. Tools that swallow errors and exit0silently corrupt your CI signal. -
Evaluate extensibility ceiling. Write a custom transform: take a
colortoken and output an additionalhsl()alias variable. If the tool’s API makes this harder than writing 30 lines of JavaScript, that friction will compound on every future custom requirement. -
Assess migration risk. Read the tool’s changelog for the past 18 months. Count breaking changes, deprecations, and open issues against your required features. A tool with infrequent releases is either extremely stable or abandoned — determine which from issue activity, not release dates alone.
Style Dictionary v4 in Depth
Style Dictionary is the reference implementation for production token compilers. Its transform pipeline is composable: you register named transforms, group them into transform groups, and assign groups to platforms.
// style-dictionary.config.mjs
import StyleDictionary from 'style-dictionary';
const sd = new StyleDictionary({
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'dist/css/',
files: [{
destination: 'tokens.css',
format: 'css/variables',
options: {
outputReferences: true,
selector: ':root'
}
}]
},
ios: {
transformGroup: 'ios-swift',
buildPath: 'dist/ios/',
files: [{
destination: 'StyleTokens.swift',
format: 'ios-swift/class.swift',
className: 'StyleTokens'
}]
},
android: {
transformGroup: 'android',
buildPath: 'dist/android/',
files: [{
destination: 'tokens.xml',
format: 'android/resources'
}]
}
}
});
await sd.buildAllPlatforms();
The outputReferences: true option is architecturally significant: it preserves alias relationships in the CSS output as var() references rather than resolved values. This is what enables runtime theming — when --color-action-primary references --color-blue-600, overriding the primitive at the theme layer automatically updates all semantic usages. The token model in Token Fundamentals & Naming Conventions explains why this matters for the primitive→semantic→component hierarchy.
Cobalt in Depth
Cobalt’s primary differentiator is that it reads DTCG JSON directly, with no configuration layer between your source file and the parser. The build config is minimal:
// cobalt.config.js
export default {
tokens: './tokens.json', // DTCG-formatted
outDir: './dist',
plugins: [
pluginCSS({
filename: 'tokens.css',
selector: ':root',
modeSelectors: {
'dark': ['[data-theme="dark"]'],
'light': ['[data-theme="light"]']
}
}),
pluginJS({
filename: 'tokens.js',
export: 'default'
})
]
};
The modeSelectors API is Cobalt’s theming primitive. You declare named modes in your DTCG source, then map each mode to a CSS selector. The plugin emits scoped CSS blocks automatically. This is the right architecture for a CSS-first, multi-theme system — but it requires that your design tooling emits DTCG with mode annotations. Review Design-to-Code Sync Workflows for how to configure Tokens Studio to emit this format on export.
Theo in Depth
Theo accepts a flat JSON or YAML source and emits format-specific files. The mental model is simple: one input format, many output formats via explicit format specifiers.
# tokens/global.yml
props:
color_action_primary:
value: "#2563eb"
type: color
category: color
spacing_base:
value: "8px"
type: size
category: spacing
global:
category: color
type: color
// theo.build.js
const theo = require('theo');
theo
.convert({
transform: { type: 'web' },
format: { type: 'css' }
})
.then((css) => {
fs.writeFileSync('dist/tokens.css', css);
});
Theo’s flat structure is readable but limiting. There is no reference resolution: if color_action_primary should reference a primitive color_blue_600, you must resolve that relationship yourself before passing the file to Theo. This means your CI pipeline carries a pre-processor step that Style Dictionary and Cobalt handle natively.
The more significant concern is maintenance. The npm package has had no active development since mid-2023. For new systems, treat Theo as a legacy option only if you are already inside the Salesforce Lightning ecosystem and cannot migrate.
CI Integration
The following GitHub Actions workflow runs a Style Dictionary v4 build. The same structure applies to Cobalt with node cobalt.config.js replacing style-dictionary build, and to Theo with its build script.
# .github/workflows/build-tokens.yml
name: Build Design Tokens
on:
push:
paths:
- 'tokens/**'
pull_request:
paths:
- 'tokens/**'
jobs:
build-tokens:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Validate token schema
run: |
npx ajv validate \
--spec=draft2020 \
-s schemas/tokens.schema.json \
-d tokens/tokens.json
- name: Build tokens
run: npx style-dictionary build --config style-dictionary.config.mjs
- name: Verify output artifacts
run: |
test -f dist/css/tokens.css || (echo "CSS output missing" && exit 1)
test -f dist/ios/StyleTokens.swift || (echo "iOS output missing" && exit 1)
grep -c 'var(--' dist/css/tokens.css | \
awk '$1 < 10 {print "Suspiciously few var() references"; exit 1}'
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: token-artifacts
path: dist/
retention-days: 30
The schema validation step runs before the build — it catches malformed DTCG or naming convention violations before they produce confusing compile errors. The verify step after the build catches a common silent failure: Style Dictionary will exit 0 even when a malformed config produces zero tokens in the output. The artifact upload gives you a downloadable build for every PR, which is essential for reviewing generated Swift or XML files that cannot be easily diffed in a browser.
Migration Cost Between Tools
Migration is rarely zero-cost, but it is also rarely as expensive as teams fear. The table below assumes you are migrating a token set of ~200–500 tokens.
| Migration Path | Effort | Key Work | Automation Potential |
|---|---|---|---|
| Theo → Style Dictionary v4 | Medium (2–4 days) | Restructure flat props into SD nested JSON; write reference aliases | High — scripted with jq or Node |
| Theo → Cobalt | Medium-High (3–5 days) | Convert to DTCG $value/$type format; map Theo categories to DTCG types |
Medium — requires type mapping table |
| Style Dictionary v3 → v4 | Low-Medium (1–3 days) | Update config to ESM; migrate removed APIs (dictionary.allProperties → dictionary.allTokens) |
High — v4 migration guide covers most cases |
| Style Dictionary v4 → Cobalt | Medium (2–4 days) | Convert SD JSON to DTCG; replace platform configs with Cobalt plugins | Medium — source JSON is structurally similar |
| Cobalt → Style Dictionary v4 | Low (1–2 days) | SD v4 reads DTCG natively; rewrite plugin configs as SD platform configs | High |
The highest-risk migration is any tool → Cobalt when your token source is not yet DTCG. The DTCG conversion is where effort concentrates: you must correctly assign $type to every token (not just color — also dimension, fontFamily, fontWeight, duration, cubicBezier, shadow, typography, border, gradient), and composite types require restructuring flat string values into objects.
For Theo → Style Dictionary migrations specifically, the deep comparison of Style Dictionary vs. Theo vs. Cobalt covers the conversion scripts and alias resolution strategy in detail.
Cross-Cluster Dependency Mapping
| Parent / Sibling | Integration Point | Validation Strategy |
|---|---|---|
| Token Scaling & CI Pipelines (parent) | Compiler output is the artifact CI distributes | Exit code validation + artifact size check |
| JSON Schema Validation for Tokens | Schema runs before compiler; catches format mismatches | ajv validate in pre-build step |
| Design-to-Code Sync Workflows | Compiler sits between Figma export and CSS artifact delivery | Format compatibility check on webhook trigger |
| Token Fundamentals & Naming Conventions | Compiler enforces naming via transform filters | Custom transform rejects tokens violating [tier]-[domain]-[role] pattern |
Diagnostic Matrix
| Diagnostic Step | Execution Detail |
|---|---|
Build exits 0 but CSS file is empty |
Run with --verbose; check that source glob resolves (npx style-dictionary build --verbose 2>&1 | grep "source") |
DTCG tokens appear as undefined in output |
Confirm compiler version supports DTCG; SD v3 silently ignores $value — upgrade to v4 |
| Alias references not resolved | Check circular reference chain with sd.hasCircularReferences(); Cobalt surfaces these as build errors automatically |
| Swift/Android output missing | Verify platform config key matches transformGroup name exactly; SD transform groups are case-sensitive |
| Theo build silently drops tokens | Theo requires category field; tokens without it are excluded without warning — validate input against Theo’s schema manually |
Root Causes and Resolutions
Empty output after SD v3 → v4 migration. Style Dictionary v4 uses ES modules by default. If your sd.config.js uses module.exports, the config is silently ignored. Rename to style-dictionary.config.mjs and use export default.
DTCG composite tokens produce string output instead of structured values. Cobalt and SD v4 both require that composite token $value fields use JSON objects, not concatenated strings. A shadow token with $value: "0 2px 4px rgba(0,0,0,0.1)" is technically invalid DTCG — it must be a structured object with offsetX, offsetY, blur, spread, and color keys.
Alias chain breaks at brand theme boundary. When two brand token files both define color.brand.primary and are merged before compilation, the last-writer-wins merge loses the first brand’s value. Use Style Dictionary’s Sets API or Cobalt’s modeSelectors to keep brand definitions namespaced rather than merging them.
Theo YAML produces wrong CSS variable names. Theo converts underscores in prop names to hyphens in CSS output: color_action_primary becomes --color-action-primary. If your codebase uses a different naming convention, Theo’s name transform is not overridable without forking the package.
Frequently Asked Questions
Which tool should I pick for a new design system from scratch?
Start with Style Dictionary v4 if you have any native mobile platforms or expect to add them. Start with Cobalt if you are CSS-first, your Figma tooling already exports DTCG, and you have no iOS/Android requirement. Avoid starting with Theo for new systems — the maintenance risk is too high to take on voluntarily.
Can I run two compilers in parallel during migration?
Yes, and this is the recommended migration strategy. Run the legacy tool on your existing token source and the target tool on the same source in parallel, diffing outputs. Once outputs are equivalent (allowing for expected naming differences), remove the legacy tool. The CI snippet above can be adapted to run both build steps in a matrix job.
Does Style Dictionary v4 fully support DTCG?
Yes, with one caveat: the v4 parser handles the $value/$type schema and DTCG composite types. However, some edge cases in the spec (particularly gradient and typography composite tokens) require explicit format handlers if your output format does not natively understand the composite structure. Check the SD v4 migration guide and the DTCG conformance test suite before declaring full compliance.
What about tools like Specify, Supernova, or Knapsack?
These are token management platforms, not compilers. They typically use Style Dictionary or a proprietary compiler under the hood for the build step. When evaluating them, ask what they use internally and what format they export — most either export DTCG or Style Dictionary-compatible JSON, which means the compiler decision still applies downstream.
Related
- Token Scaling, Validation & CI Pipelines — parent overview covering the full pipeline from design export to deployed CSS artifacts
- Style Dictionary vs. Theo vs. Cobalt: Deep Compiler Comparison — line-by-line transform configs, benchmark results, and migration scripts for all three tools
- Design-to-Code Sync Workflows — configuring the Figma → compiler → CSS artifact pipeline that the chosen tool sits inside
- Token Fundamentals & Naming Conventions — the three-tier token model that your compiler’s transform filters must enforce