Choosing a Token Compiler: A Decision Guide

Part of Token Compiler Comparison. The feature comparison tells you what each tool does. This guide is about making the decision — which questions decide it, how to weight them, and how to run a spike that produces evidence rather than opinion.

Five questions that decide a token compiler choice Five decision inputs listed in order of weight: existing investment, output platforms, source format, type generation needs, and team capacity for custom plugin work. 1 · What are we already running, and what does switching cost consumers? 2 · Which platforms consume the output — web only, or native too? 3 · What shape is the source, and who edits it? 4 · Do consumers need generated types? 5 · Who will maintain custom transforms once the person who wrote them moves on?
The first question usually settles it. The remaining four matter mostly for greenfield systems, which are the minority.

Prerequisites Link to this section

  • A representative sample of the real token set — at least one composite type, one alias chain and one theme, since those are where compilers differ.
  • A list of every consumer and what format each one needs.
  • A rough count of custom transforms currently in use, if migrating.
  • Two or three days of one engineer’s time for the spike described below; anything shorter produces a guess wearing evidence’s clothes.

Step 1 — Answer the Reversibility Question First Link to this section

Before comparing anything, establish what a wrong choice costs. The answer depends almost entirely on one thing: whether the candidate compilers emit the same variable names.

Two compilers producing --color-action-primary from the same source are interchangeable behind a build script. Switching between them is a configuration change that no consumer notices, so the decision is low-stakes and can be revisited cheaply. A compiler that emits --COLOR_ACTION_PRIMARY instead makes the switch a breaking change for every consumer, a major version, and a coordinated migration.

Establish this before evaluating features, because it changes how much the evaluation is worth. A reversible decision deserves an afternoon; an irreversible one deserves the full spike.

Step 2 — Score Against Weighted Criteria Link to this section

// scripts/score-compilers.mjs — weights reflect what the organisation actually needs.
const WEIGHTS = {
  existingInvestment: 5,   // migration cost for consumers and CI
  platformCoverage:   4,   // does it emit what every consumer needs
  sourceFormat:       3,   // DTCG native, or an adapter to maintain
  typeGeneration:     2,   // nice to have unless consumers are typed
  transformMaturity:  3,   // custom value transforms are common at scale
  maintenance:        3,   // release cadence, issue response, bus factor
};

export const score = (tool) =>
  Object.entries(WEIGHTS).reduce((sum, [k, w]) => sum + w * (tool[k] ?? 0), 0);

Why weighting matters: an unweighted feature matrix makes type generation look as important as platform coverage, which is true for approximately no organisation. Writing the weights down forces the conversation that actually needs having — what do we need this to do — and it makes the eventual decision defensible to whoever asks about it in six months.

Fill the scores from the spike rather than from documentation. Every tool’s documentation describes its happy path, and the differences that matter appear at the edges.

Decision paths by starting position Three starting positions — an existing Style Dictionary setup, a greenfield web-only system, and a multi-platform system — each leading to a recommended choice and the condition that would change it. Already on Style Dictionary stay, add the DTCG parser the migration cost is real and the feature gap is not reconsider if: you need first-class generated types Greenfield, web only Cobalt — native DTCG, typed output, minimal config smallest setup cost reconsider if: native platforms appear later Multi-platform Style Dictionary — widest platform and transform coverage by some distance reconsider if: web is the only platform that matters
Three starting positions cover nearly every real case, and each has a default answer plus one condition that would overturn it.

Step 3 — Run the Spike Against Real Tokens Link to this section

Intent: the differences that matter show up on your token set, not on a tutorial’s.

Take a genuine slice — around fifty tokens including a composite shadow, a three-hop alias chain, one theme override and one brand override — and compile it with each candidate. Record five things:

  • The exact variable names emitted, character for character.
  • Whether the composite type compiled without a custom transform.
  • Whether the alias chain survived as var() references or was flattened.
  • How long the build took, cold and warm.
  • How many lines of configuration were required to get there.

The last is a better proxy for long-term maintenance cost than any feature comparison. A tool needing eighty lines of configuration to produce the same output as another tool’s twelve is telling you something about the next five years.

# Same input, three outputs, one comparison.
node sd.config.mjs        && cp dist/sd/variables.css      spike/sd.css
npx co build              && cp dist/cobalt/tokens.css     spike/cobalt.css
node theo.config.js       && cp dist/theo/variables.css    spike/theo.css

diff <(grep -o '^\s*--[a-z0-9-]*' spike/sd.css | sort) \
     <(grep -o '^\s*--[a-z0-9-]*' spike/cobalt.css | sort)

Step 4 — Weigh the Ecosystem, Not Only the Tool Link to this section

A compiler is a dependency that will need upgrading for years. Three signals predict how that goes better than any feature does.

Release cadence and breaking-change history. A tool that has shipped two major versions in three years, each requiring a config rewrite, will do it again. Check the changelog for how migrations were communicated and whether codemods were provided.

Plugin ecosystem depth. The transforms you need on day one are the documented ones. The transform you need in month eight — a bespoke colour space conversion, a platform-specific naming rule — is either in the ecosystem or is yours to write and maintain.

Who else is running it at your scale. A tool that is popular in small projects and unused in large ones may have a scaling characteristic nobody has documented. Build time on a ten-thousand-token set is the usual one, and it is worth measuring in the spike if the token set is large.

Step 5 — Write Down the Decision and Its Expiry Link to this section

Intent: a decision record turns the choice into something reviewable rather than something inherited.

Record what was chosen, the weights used, the spike results, and — most usefully — the condition that would change the answer. “We chose Cobalt because we are web-only and want generated types; if we ship a native mobile client, this should be revisited” is a genuinely useful sentence to find two years later, and it prevents both the re-litigation of a settled decision and the indefinite persistence of one whose premises have expired.

Include the reversibility assessment from step 1 in the record. Knowing that a switch would be a configuration change rather than a breaking release is what makes the eventual revisit tractable.

What the spike measures against what documentation claims Four evaluation criteria compared between what the documentation states and what the spike reveals, showing that emitted names, composite handling and configuration size differ from expectations. criterion docs say spike shows DTCG support "supported" composites need a transform naming "configurable" default differs by tool config size not stated 12 lines vs 80 lines
Every row where documentation and spike disagree is a row that would have become a surprise in month three.

Verification Link to this section

The spike itself is the verification, and it should end with a concrete artefact: three compiled CSS files from one source, and a diff between them. If the diffs are empty apart from banner comments, the choice is genuinely reversible and can be made quickly on secondary criteria. If they are not, the diff is the migration cost, quantified.

Run one further check before committing: compile the full token set, not just the slice. Scaling problems and unsupported edge cases concentrate in the tokens nobody thought to include in a sample — the one shadow with four layers, the one alias that crosses a brand boundary, the token whose name collides with a reserved word in one output format.

Troubleshooting Link to this section

Symptom Likely cause Fix
Every tool scores about the same The weights are flat, or the criteria are all “supported” Re-weight against what the organisation actually needs; score from the spike
The spike produces identical output The token slice avoided the interesting cases Add a composite, an alias chain and a theme override, which is where tools differ
The decision keeps being re-opened No decision record, or no stated expiry condition Write both; most re-litigation is caused by the reasoning being unavailable
Migration looks cheap and turns out expensive Only the CSS output was compared, not the type or documentation outputs Compare every artefact every consumer depends on
The chosen tool cannot express a needed transform Transform maturity was scored from documentation Write the awkward transform during the spike, not after the decision

Migration Note Link to this section

If the decision is to switch, sequence it so the switch itself is boring. Run both compilers in parallel in CI for a release cycle, diffing their outputs on every build. When the diff has been empty for a week across real changes, the switch is a one-line change to which output is published.

Keep the old configuration in the repository, unused, for one further release. It costs nothing and it makes a rollback a revert rather than a reconstruction — which matters because the failure mode of a compiler switch is usually discovered by a consumer, days later, in an output nobody was diffing.

The Decision Nobody Regrets Link to this section

Across the systems this guide is drawn from, one pattern holds regardless of which tool was chosen: teams that kept their token source clean and standards-compliant were happy with any compiler, and teams that let tool-specific conventions leak into the source were unhappy with all of them.

That suggests where the effort actually belongs. The source file is the long-lived artefact — it will outlast at least one compiler, probably two — and keeping it in the standard shape, with no tool-specific keys and no assumptions about how a particular transform behaves, is what preserves the freedom to change tools at all.

The corollary is that the adapter layer discussed in the sync workflows is not merely convenient. A single place where tool-specific shapes are normalised into the canonical source, and a single place where the canonical source is compiled into outputs, means a compiler swap touches one of those two places and nothing else. Systems without that separation find that the compiler’s conventions have propagated into the token files themselves, at which point the choice really has become irreversible — not because of the output names, but because the input is now written in a dialect.