Design token versioning
March 17, 2026
A product team renames color.primary.default to color.interactive.default. The intent is clarity. The rename takes 30 seconds. The coordination it triggers takes three weeks. Two consumer apps break in staging. One breaks silently in production because the old CSS variable falls back to inherit and nobody notices until a user files a bug about a transparent button.
This is not a rare edge case. It is what happens every time a design system team treats token names as internal labels rather than the public API they actually are.
Why token breakage is so much worse than code breakage
Rename a function and the build fails. TypeScript surfaces the error before anything ships. Token changes do not work like this. Tokens resolve at runtime through CSS custom properties or theme providers. A missing token does not crash the build. It falls back silently. The wrong color renders, or nothing renders, and it reaches production looking like a mystery.
This is why versioning discipline has to come before the incident, not after. The absence of a visible error signal is not permission to treat token names as changeable. It means the damage is invisible until it is widespread.
Applying semver to tokens
Semantic versioning maps onto token changes cleanly once you are honest about what "breaking" means for your consumers.
A patch changes a value without changing the name or type. Adjusting color.surface.primary from #0f172a to #0d1526 is a patch. Every consumer keeps working. They just get a slightly different shade, automatically.
A minor adds tokens without touching existing ones. New steps in a color scale, additional spacing values, a new semantic role. All minor. Existing consumers are unaffected.
A major removes, renames, or changes the type of an existing token. This is where things break. Any consumer that has not updated will produce a regression when they pull the new version.
What actually makes a change breaking
The useful heuristic: if a consumer app that has not changed its own code renders differently after receiving your update, you made a breaking change. In practice this means:
- Renaming a token — the old CSS variable disappears from the output
- Deleting a token with no replacement
- Changing a token from one type to another
- Moving a token to a different path, even if the value is identical
Value updates, new tokens, and description changes are all safe. The contract is the name. Not the value.
The most common source of unnecessary major bumps is poor naming upfront — names chosen for how a token looks right now rather than what it represents. Choosing color.blue-600 instead of color.action.primary means you will rename it the moment the brand changes. See the post on token naming conventions for how to structure names that survive system growth without needing to change.
Knowing what you are about to break before you ship it
Before any publish, diff the current state against the last released version. reframe diff lists every token that was added, modified, or removed since the last publish. You see exactly what has changed before you decide on a bump type.
The bump type itself is yours to choose. reframe publish --bump major ships a major. The diff gives you the facts. The judgment about whether your consumers are ready is still yours — because a tool cannot make that call. What a team managing five small apps can absorb in a week might take a platform team coordinating twenty consumer codebases a full quarter to roll out safely.
Branches are the other side of this. Work-in-progress changes stay on a branch until you are ready to publish. No consumer ever receives a half-finished rename. The branch is the staging environment for the token contract — changes are reviewable before they become anyone else's problem.
Shipping breaking changes without causing chaos
The cleanest pattern for a breaking rename: keep the old token alive for one release, aliased to the new name. Mark it deprecated. Give consumers a migration window. Remove the old name in the next major. This turns a sudden breakage into a scheduled deprecation, which is a very different thing to coordinate with other teams.
For large-scale renames, codemods are worth the investment. A script that specifically targets token references — not all string occurrences — is far more reliable than project-wide find and replace. Ship the codemod alongside the changelog so consumers can migrate in minutes rather than afternoons.
The version number is a communication tool. When every consumer knows that a patch requires no action, a minor is safe to pull anytime, and a major requires a migration before the next deploy, the overhead of publishing drops significantly. The number carries the message. You stop having to explain it in Slack every time something ships.
There is a deeper shift that comes with treating tokens as versioned API. It forces the design system team to think about consumers before making changes, not after. That shift in instinct is worth more than any tooling.