Introduction
Versioning your design system is how you communicate change to consumers. A version number tells teams whether they can upgrade safely, what broke, and what's new. Get versioning wrong and teams either pin to old versions forever or break their apps on update.
Semantic versioning (semver) is the standard: MAJOR.MINOR.PATCH. But knowing what constitutes a breaking change in a component library is nuanced — is changing a default prop value a breaking change? Is renaming a CSS class?
This guide covers semver for component libraries, automated changelogs, release processes, and migration guides for breaking changes.
Key Concepts
Semver for Component Libraries
// What counts as a breaking change (MAJOR)?
// - Removing a component or prop
// - Changing a prop type
// - Changing default behavior
// - Removing a CSS class or custom property
// - Changing the DOM structure that consumers rely on
// What's a new feature (MINOR)?
// - Adding a new component
// - Adding a new prop with a default value
// - Adding a new variant
// - New CSS custom properties
// What's a patch (PATCH)?
// - Bug fixes
// - Visual fixes that match the spec
// - Performance improvements
// - Documentation updates
Changesets for Automation
# Install changesets
npm install @changesets/cli
# Add a changeset (run per PR)
npx changeset
# > Which packages? @myui/react
# > What type? minor
# > Summary: Added destructive variant to Button
# Generated .changeset/happy-cats-fly.md:
---
"@myui/react": minor
---
Added destructive variant to Button component.
Use `<Button variant="destructive">` for dangerous actions.
Practical Examples
1. Release Workflow with Changesets
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- name: Create Release PR or Publish
uses: changesets/action@v1
with:
publish: npm run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
2. Migration Guide Template
# Migrating from v2 to v3
## Breaking Changes
### Button: `type` prop renamed to `variant`
**Before:**
```tsx
<Button type="primary">Save</Button>
After:
<Button variant="primary">Save</Button>
Codemod:
npx @myui/codemods v3-button-variant
Input: Removed onChange string handler
The onChange prop now receives the native event, not just the value string.
### 3. Automated Codemod
```typescript
// codemods/v3-button-variant.ts
import type { API, FileInfo } from 'jscodeshift';
export default function transform(file: FileInfo, api: API) {
const j = api.jscodeshift;
return j(file.source)
.findJSXElements('Button')
.find(j.JSXAttribute, { name: { name: 'type' } })
.forEach(path => {
path.node.name.name = 'variant';
})
.toSource();
}
Best Practices
- ✅ Use changesets for automated versioning and changelogs
- ✅ Document every breaking change with before/after code examples
- ✅ Provide codemods for mechanical migrations
- ✅ Use pre-release versions (alpha, beta, rc) for testing major changes
- ✅ Announce upcoming breaking changes one minor version early with deprecation warnings
- ❌ Don't release breaking changes without a migration guide
- ❌ Don't batch unrelated breaking changes — consumers can't adopt incrementally
Common Pitfalls
- Accidental breaking changes — CSS class name changes break consumers who target them
- Version fatigue — too many major versions erode trust; batch thoughtfully
- No pre-release testing — consumers discover issues only in production
- Incomplete changelogs — 'bug fixes and improvements' helps nobody
Related Guides
- Building Component Libraries — What you're versioning
- Design System Governance — Decision process for breaking changes
- Design System Documentation — Changelog and migration docs
- Next.js Deployment Strategies — CI/CD for library publishing