Apply semantic versioning to design system packages — define breaking changes for components and tokens, automate version bumps, and build deprecation workflows with migration guides.
Apply semantic versioning to design system packages — define breaking changes for components and tokens, automate version bumps, and build deprecation workflows with migration guides.
Semver definitions (patch = bug fix, minor = new feature, major = breaking change) need precise mapping to design system artifacts. Patch changes include fixing a visual bug like a wrong border radius, correcting a documentation typo, or adding a missing aria attribute. Minor changes include new component variants such as adding size='xl' to Button, new optional props, or new token aliases. Major breaking changes include removing or renaming a prop, changing a token's semantic meaning where color.brand.primary shifts from blue to teal, changing a component's rendered HTML structure which affects CSS selectors in consuming apps, or dropping browser support. A common mistake: treating visual-only token value changes as patches. Token values ARE breaking changes for pixel-perfect test suites and must be major bumps or accompanied by explicit migration guides. Document your definition of breaking changes in the contributing guide to prevent disagreements during PR review.
Large design systems split into multiple independently versioned packages: @acme/tokens for design tokens, @acme/react for React components, @acme/icons for the SVG icon set, and @acme/css for base styles. Each package has its own version — @acme/[email protected] can release independently of @acme/[email protected]. This granularity lets consuming teams upgrade tokens without taking a component major version. The trade-off is peer dependency management complexity. Solve this by publishing an @acme/design-system meta-package that re-exports everything with a curated, tested version matrix.
1{
2 "name": "@acme/design-system",
3 "version": "5.0.0",
4 "description": "Meta-package: tested combination of all design system packages",
5 "peerDependencies": {
6 "@acme/tokens": "^3.1.0",
7 "@acme/react": "^2.5.0",
8 "@acme/icons": "^1.8.0"
9 },
10 "devDependencies": {
11 "@acme/tokens": "3.1.2",
12 "@acme/react": "2.5.4",
13 "@acme/icons": "1.8.1"
14 }
15}Teams import from @acme/design-system and get a tested, compatible combination without managing individual peer versions.
Never remove a prop in a major release without a deprecation minor first. The deprecation cycle has three steps: first introduce the new API alongside the old one; second add a runtime console.warn on the deprecated prop linking to the migration guide; third after one major version and a minimum of 90 days, remove the old API.
1import { useEffect } from 'react';
2
3function useDeprecatedProp(
4 prop: unknown,
5 oldName: string,
6 newName: string,
7 migrationUrl: string
8) {
9 useEffect(() => {
10 if (prop !== undefined && process.env.NODE_ENV !== 'production') {
11 console.warn(
12 `[DesignSystem] '${oldName}' is deprecated. ` +
13 `Use '${newName}' instead. ` +
14 `Migration guide: ${migrationUrl}`
15 );
16 }
17 }, [prop]);
18}
19
20// Usage in Button component
21export function Button({ variant, kind, ...props }: ButtonProps) {
22 useDeprecatedProp(kind, 'kind', 'variant',
23 'https://ds.acme.com/migration/v6#button-kind');
24 const resolvedVariant = variant ?? kind;
25 return <button data-variant={resolvedVariant} {...props} />;
26}Warnings only appear in development mode, keeping production bundles clean. The migration URL must point to a live guide on release day.
A good changelog tells consuming teams exactly what to do, not just what changed. Each breaking change entry must have: the impact scope (who is affected), Before and After code samples, numbered migration steps with copy-pasteable commands, and the reason why the change was necessary. Structure changelogs by version with sections for Breaking Changes, New Features, Bug Fixes, and Deprecations.
1## [6.0.0] — 2026-03-15
2
3### Breaking Changes
4
5#### Button: `kind` prop removed (replace with `variant`)
6
7The `kind` prop was deprecated in v5.2.0 and is now removed.
8
9**Before:**<Button kind="primary">Submit</Button>
1
2**After:**<Button variant="primary">Submit</Button>
1
2**Automated migration:**npx @acme/[email protected] button-kind-to-variant ./src
1
2See the [full v6 migration guide](https://ds.acme.com/migration/v6) for
3manual migration steps and edge cases.Design token renames are breaking changes because consuming code references token names directly in CSS, JS, and design tools. When renaming --color-brand-primary to --color-brand-core: publish a minor with both names present where --color-brand-core is new and --color-brand-primary maps to it via CSS alias. Log a deprecation message in the Style Dictionary transform. After 90 days, publish a major that removes the alias and run a migration script across consuming repos.
1// scripts/migrate-tokens.mjs
2import { readFileSync, writeFileSync } from 'fs';
3import { glob } from 'glob';
4
5const RENAMES = {
6 '--color-brand-primary': '--color-brand-core',
7 '--color-brand-secondary': '--color-brand-accent',
8 '--color-feedback-danger': '--color-status-error',
9};
10
11async function migrateTokens(pattern) {
12 const files = await glob(pattern);
13 let changedCount = 0;
14
15 for (const file of files) {
16 let content = readFileSync(file, 'utf8');
17 let changed = false;
18
19 for (const [oldName, newName] of Object.entries(RENAMES)) {
20 if (content.includes(oldName)) {
21 content = content.replaceAll(oldName, newName);
22 changed = true;
23 }
24 }
25
26 if (changed) {
27 writeFileSync(file, content);
28 changedCount++;
29 }
30 }
31
32 console.log(`Migrated ${changedCount} files.`);
33}
34
35await migrateTokens('src/**/*.{css,scss,tsx,ts}');Ship this script alongside the major release so teams can run it with a single command.
Audit your design system for any props or token names scheduled for removal. Write a deprecation minor release with runtime warnings and a migration guide before cutting the major version.
Your progress is saved in this browser, on this device only.