Skip to content
Design

Design System Versioning

Semantic versioning, changelogs, and migration guides for component library releases.

stellae.design

7 min read

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

plain
// 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

bash
# 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

yaml
# .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

markdown
# Migrating from v2 to v3

## Breaking Changes

### Button: `type` prop renamed to `variant`
**Before:**
```tsx
<Button type="primary">Save</Button>

After:

tsx
<Button variant="primary">Save</Button>

Codemod:

bash
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

Designdesign

Was this article helpful?