| name | design-tokens-versioning |
| description | Semantic versioning for design tokens — deprecation, breaking changes, and migration. Use this when releasing token changes or planning a major bump. |
Design Tokens Versioning
Instructions
Tokens are a public API. Treat them with the same SemVer discipline as any library: additive changes are minor, breaking changes are major, and every breaking change ships with a migration path.
1. SemVer for Tokens
| Change | Bump |
|---|
| New token added | minor |
| New theme / brand added | minor |
| New variant on existing component token | minor |
| Token value change that preserves semantics | patch |
| Token value change that alters semantics or contrast | minor |
| Token rename with alias to old name | minor |
| Token rename without alias | major |
| Token removal (after deprecation window) | major |
$type change (color → shadow, etc.) | major |
| Layer reorganization that changes public-facing token paths | major |
"Preserve semantics": the same element continues to look the same to a user (e.g., a hex value nudged for contrast compliance within the same role).
2. Deprecation Protocol
Every removal ships through deprecation.
Step 1 — Annotate:
{
"color": {
"brand": {
"50": {
"$value": "{color.brand.100}",
"$deprecated": true,
"$description": "Deprecated since v3.2.0. Use color.brand.100.",
"$extensions": {
"com.designsystem.deprecation": {
"replacement": "color.brand.100",
"removeInVersion": "4.0.0"
}
}
}
}
}
}
Step 2 — Warn on use: emit platform-native warnings in generated output.
@Deprecated("Use DsTokens.Color.Brand100", ReplaceWith("DsTokens.Color.Brand100"))
val Brand50 = Color(0xFF...)
@available(*, deprecated, renamed: "DSTokens.Color.brand100")
public static let brand50 = UIColor(...)
@Deprecated('Use DsTokens.color.brand100')
static const brand50 = Color(0xFF...);
Step 3 — Ship one full minor cycle before removal. For high-traffic tokens, two minors.
Step 4 — Codemod: publish a codemod (ESLint rule, ktlint rule, dart-fix script, or shell sed) that rewrites consumer code.
3. Aliases as Soft Rename
When renaming, keep the old path as an alias for one version:
{
"color": {
"action": {
"primary": {
"bg": { "$value": "{color.brand.40}" },
"default": { "$value": "{color.action.primary.bg}", "$deprecated": true }
}
}
}
}
4. Breaking Change Checklist
Before merging a major bump:
5. Versioning Generated Artifacts
Token packages follow the repo version:
com.ds:ds-tokens:3.2.0
DSTokens Swift Package tag v3.2.0
ds_tokens: ^3.2.0 on pub.dev
@ds/tokens@3.2.0 on npm
Do not publish one platform at a different version than the others. The same repo tag produces all four artifacts atomically.
6. Release Notes
Group notes by audience and layer. A consumer developer cares about different things than a DS contributor.
## 3.3.0 — 2026-05-14
### Added
- `color.feedback.info.*` tokens.
- Component: `Callout`.
### Deprecated
- `color.brand.50` (use `color.brand.100`). Removed in 4.0.
### Fixed
- Contrast of `color.content.muted` on `color.surface.subtle` in dark theme (was 3.8:1, now 4.6:1).
7. Tooling
- changesets (
@changesets/cli): per-PR markdown fragments are aggregated at release; emits SemVer bump and changelog.
- token-lint / spectral: custom rules for "a removed token must have
$deprecated in the previous minor."
- ts-migrate / jscodeshift / openrewrite / dart-fix: run codemods platform by platform.
8. Consumer-Side Policy
Document what consumers should expect:
- Pin to
^X.Y.Z so minor and patch updates flow; majors are opt-in.
- Major upgrades come with a published migration guide and codemod.
- Renovate / Dependabot PRs for patch/minor auto-merge after CI passes.
9. Anti-Patterns
- Shipping a token rename as a patch because "it's just a rename."
- Removing a token in the same version it was deprecated.
- Changing a
$value from light blue to red and calling it patch.
- Multiple token package versions across platforms (Android on 3.2, iOS still on 2.9).
- No codemod for breaking changes — forces manual rewrites across every consumer.
Checklist