Versioning & Quản lý ReleaseVersioning & Release Management
Thuộc bộ kiến thức Design System Roadmap.
Tổng quan
Design system không chỉ là một thư viện nội bộ bình thường — nó là dependency được toàn bộ các team sản phẩm trong tổ chức dùng chung cùng một lúc. Khi một service backend thông thường ship một breaking change, chỉ một team chịu ảnh hưởng. Nhưng khi design system ship một breaking change mà không được thông báo trước, hàng chục team có thể bị ảnh hưởng cùng lúc, thường là ngay trên production, thường vào thời điểm tệ nhất (một đợt release freeze, một buổi demo, một ca trực on-call mà không ai am hiểu nội bộ design system). Đây chính là lý do design system cần kỷ luật versioning và release management nghiêm ngặt hơn hầu hết phần mềm khác: bán kính ảnh hưởng (blast radius) của một sai sót là toàn tổ chức, không chỉ một team.
Thực hành versioning và release tốt biến câu chuyện “team design system đổi gì đó và làm hỏng chúng tôi” thành “chúng tôi đã xem changelog, chạy codemod, và upgrade theo lịch của riêng mình.” Các cơ chế giúp điều này khả thi — semantic versioning, chính sách deprecation, changelog, các kênh pre-release, và kế hoạch rollback — là nội dung chính của bài viết này.
Kiến thức nền tảng
Tại sao kỷ luật versioning ở đây quan trọng hơn nơi khác
Ba đặc điểm của design system khiến versioning trở nên đặc biệt rủi ro cao:
- Fan-out (lan tỏa). Một package component đơn lẻ (ví dụ
@acme/react-components) có thể được cài trong hơn 50 repo sản phẩm. Một thay đổi tưởng chừng nhỏ với team design system sẽ bị nhân lên với mọi consumer. - Sự gắn kết về mặt visual và hành vi. Thay đổi trong design system thường ảnh hưởng đến pixel, spacing, và hành vi tương tác trên nhiều màn hình cùng lúc — một lỗi không chỉ giới hạn trong giá trị trả về của một hàm, mà giới hạn trong cách khách hàng cảm nhận toàn bộ sản phẩm.
- Niềm tin chính là sản phẩm. Nếu các team không còn tin rằng upgrade là an toàn, họ sẽ ghim (pin) phiên bản cũ mãi mãi và design system bị phân mảnh — chính là kết quả mà nó được xây dựng để ngăn chặn. Versioning đáng tin cậy là thứ giữ cho việc áp dụng (adoption) diễn ra tự nguyện và liên tục.
Semantic Versioning (SemVer)
Hệ sinh thái design system đã đồng thuận sử dụng Semantic Versioning làm quy ước chuẩn: số phiên bản có dạng MAJOR.MINOR.PATCH (ví dụ 4.3.1), và mỗi phần đều có ý nghĩa hợp đồng (contract) rõ ràng.
| Phần | Ý nghĩa | Ví dụ trong design system |
|---|---|---|
| PATCH | Sửa lỗi tương thích ngược (backwards-compatible), không thay đổi API hay visual-contract | Sửa lỗi visual khi focus ring của Button bị clip bởi overflow: hidden của phần tử cha. 4.3.0 → 4.3.1 |
| MINOR | Thêm tính năng mới, vẫn tương thích ngược | Thêm variant mới size="xl" cho Button, các chỗ dùng cũ không bị ảnh hưởng. 4.3.1 → 4.4.0 |
| MAJOR | Thay đổi không tương thích ngược (backwards-incompatible) | Đổi tên prop type của Button thành variant, hoặc đổi default behavior của Modal từ closeOnOverlayClick mặc định true sang false. 4.4.0 → 5.0.0 |
Giá trị của SemVer là nó cho phép một team consumer đánh giá rủi ro upgrade mà không cần đọc code — một dependency range ^4.3.0 có thể tự động nhận patch và minor release, trong khi major bump đòi hỏi một lần upgrade có chủ đích, được review kỹ. Đây là lý do design system phải nghiêm ngặt về những gì được tính là “breaking”: bất cứ điều gì có thể buộc consumer phải sửa code, sửa markup, sửa visual regression baseline, hay sửa accessibility tree đều được tính là breaking, kể cả khi phần implementation bên dưới “chỉ là CSS.”
Package design token cũng cần kỷ luật tương tự. Đổi tên color.brand.primary thành color.brand.500 là một thay đổi MAJOR đối với package tokens, dù không có logic component nào thay đổi — bất kỳ ai dùng trực tiếp tên token cũ đều sẽ bị vỡ (break).
Phiên bản pre-1.0 và lưu ý về “0.x”
Theo spec của SemVer, 0.y.z dành riêng cho giai đoạn phát triển ban đầu, và mọi thứ có thể thay đổi bất cứ lúc nào — không có cam kết ổn định nào. Nhiều design system chủ động ở lại 0.x trong vài tháng đầu khi API component vẫn đang trong quá trình khám phá, và coi 1.0.0 là thời điểm chính thức cam kết ổn định API. Hãy nêu rõ điều này trong README để những người dùng sớm (early adopter) không nhầm tưởng 0.x có cùng mức bảo đảm như 1.x.
Khái niệm chính
Monorepo và multi-repo cho design system nhiều package
Design system hiếm khi chỉ là một package duy nhất — một thiết lập điển hình gồm design tokens, thư viện component React (hoặc Vue/Web Components), package icon, và đôi khi cả plugin Figma hay trang tài liệu. Vị trí các package này được đặt ở đâu sẽ có hệ quả thực sự đến versioning.
| Khía cạnh | Monorepo (ví dụ dùng Nx, Turborepo, Lerna/Changesets) | Multi-repo (mỗi package một repo riêng) |
|---|---|---|
| Versioning | Có thể đồng bộ hóa (tokens, react, icons cùng lên 5.0.0) hoặc độc lập theo từng package với tooling chung | Hoàn toàn độc lập theo repo; không có cơ chế đồng bộ tự động |
| Thay đổi liên package | Dễ dàng gộp một PR cập nhật đồng thời tokens và các component dùng tokens đó | Cần phối hợp PR qua nhiều repo, dễ bị lệch nhau |
| CI/CD | Một pipeline có thể build, test, publish mọi package nhất quán | Mỗi repo cần pipeline riêng, nhiều trùng lặp |
| Khả năng tra cứu | Một nơi duy nhất để tìm lịch sử, issue, code | Contributor phải biết repo nào sở hữu phần nào |
| Nhiễu release | Thay đổi icons có thể buộc phải release cả package tokens nếu version bị khóa đồng bộ (trừ khi dùng independent versioning) | Consumer chỉ thấy release của package họ dùng |
| Tooling phổ biến | Changesets là chuẩn de facto cho versioning + sinh changelog trong monorepo | Tag thủ công hoặc semantic-release theo từng repo |
Phần lớn design system đang được bảo trì tích cực (ví dụ Shopify Polaris, Atlaskit của Atlassian) dùng monorepo với versioning độc lập cho từng package — mỗi package có số SemVer riêng, nhưng được build, test, và release từ cùng một repo, dùng công cụ như Changesets để theo dõi package nào thay đổi trong một PR và tự sinh changelog cho từng package. Cách này lấy được phần lớn lợi ích atomicity của monorepo (tokens và components có thể thay đổi cùng nhau) mà không buộc mọi consumer phải bump mọi package trong mỗi lần release.
Chiến lược release: nhịp độ và các kênh
Nhịp độ (cadence). Có hai triết lý chính:
- Release liên tục / thường xuyên, quy mô nhỏ — mỗi PR merge có thay đổi vào package có thể kích hoạt một patch hoặc minor release (thường tự động hóa qua
semantic-releasehoặc action release của Changesets). Cách này giữ thay đổi nhỏ, dễ review, và giúp bản sửa lỗi đến tay consumer trong vài giờ. - Release theo lịch, gộp đợt — các thay đổi tích lũy trên một release branch và được xuất bản theo nhịp (hàng tuần, hai tuần một lần, hoặc theo sprint). Cách này giúp team consumer dễ lên kế hoạch hơn (“design system release mỗi thứ Ba cách tuần”) và giảm cảm giác mệt mỏi khi phải upgrade nhiều lần một ngày, đánh đổi bằng việc sửa lỗi đến chậm hơn.
Nhiều team chọn ở giữa: patch release được xuất bản liên tục (việc sửa lỗi không nên bị giữ lại chờ lịch), trong khi minor và major release đi theo nhịp độ được thông báo trước, có thể dự đoán.
Các kênh pre-release. Không phải consumer nào cũng muốn sự ổn định vào cùng một thời điểm — một số team sẵn sàng thử các thay đổi bleeding-edge để đổi lấy quyền tiếp cận sớm hoặc ảnh hưởng đến API cuối cùng. Cơ chế npm dist-tag chuẩn hỗ trợ điều này:
| Kênh | Ví dụ npm dist-tag | Đối tượng |
|---|---|---|
| Stable | 4.4.0 (latest) | Mặc định cho mọi consumer |
| Release candidate | 5.0.0-rc.1 (next) | Team chuẩn bị upgrade trước khi major GA ra mắt |
| Beta | 5.0.0-beta.3 (beta) | Team sẵn sàng thử tính năng sắp tới, chấp nhận một số bất ổn |
| Alpha / canary | 5.1.0-canary.20260719 (canary) | Team design system tự dogfooding, hoặc các team nội bộ ưa mạo hiểm; build từ mọi merge vào main |
Xuất bản pre-release cho phép team design system kiểm chứng một breaking change trên code consumer thực tế trước khi nó trở thành latest mặc định, giúp phát hiện các vấn đề mà unit test bên trong repo design system không bao giờ thấy được.
Chiến lược deprecation
Quy tắc quan trọng nhất trong deprecation của design system: không bao giờ xóa bỏ (remove) một thứ trong cùng release mà nó được đánh dấu deprecated. Deprecation và removal là hai sự kiện tách biệt, cách nhau bởi một khoảng thời gian ân hạn (grace period) đủ dài để mọi team consumer di chuyển theo lịch riêng của họ.
Một chuỗi deprecation lành mạnh trông như sau:
- Thông báo & cảnh báo (MINOR release). API cũ vẫn hoạt động y như trước, nhưng việc sử dụng nó giờ sẽ kích hoạt một cảnh báo — console warning lúc runtime (ví dụ: prop
typecủaButtonđã deprecated, dùngvariantthay thế), tag JSDoc@deprecatedtrong TypeScript mà linter/IDE hiển thị, và một mục trong changelog. - Thời gian ân hạn (grace period). Một khoảng thời gian được nêu rõ (thường là một đến hai chu kỳ minor/major release, hoặc một số tháng cố định) trong đó cả API cũ và mới cùng hoạt động song song. Team design system nên theo dõi số liệu sử dụng nội bộ (usage analytics) nếu có thể để biết bao nhiêu consumer đã migrate.
- Cung cấp lộ trình migration. Một hướng dẫn migration bằng văn bản mô tả chính xác thay đổi gì và tại sao. Với các thay đổi mang tính cơ học thuần túy (đổi tên prop, đổi đường dẫn import), hãy cung cấp một codemod — một script (thường xây trên jscodeshift) tự động viết lại code của consumer, biến việc phải mất hàng giờ tìm-và-thay-thế thủ công thành một lệnh duy nhất như
npx @acme/design-system-codemods rename-button-prop. - Xóa bỏ (MAJOR release). Chỉ khi grace period đã kết thúc — và lý tưởng là khi telemetry sử dụng cho thấy API deprecated gần như không còn ai dùng — thì major version tiếp theo mới thực sự xóa API cũ. Việc xóa bỏ này bản thân nó cũng là một breaking change và phải được ghi rõ trong changelog.
Chính trình tự này giúp design system tiến hóa API mà không bao giờ buộc 40 team sản phẩm phải migrate khẩn cấp trong cùng một ngày.
Changelog
Changelog là bề mặt giao tiếp chính giữa team design system và mọi consumer, và nó xứng đáng được chăm chút không kém gì bản thân code. Định dạng Keep a Changelog là quy ước được áp dụng rộng rãi:
- Mỗi package có một
CHANGELOG.mdriêng (hoặc theo release, nếu dùng Changesets, công cụ này tự sinh và prepend các mục mới). - Các mục được nhóm theo tiêu đề version và ngày tháng, tiếp tục nhóm theo hạng mục:
Added,Changed,Deprecated,Removed,Fixed,Security. - Một mục
Unreleasedở đầu tập hợp các thay đổi đã merge nhưng chưa được ship.
Một mục changelog tốt trả lời ba câu hỏi, không chỉ một:
- Thay đổi gì — chính xác và cụ thể (“
Button: sửa lỗi focus ring bị clip khi phần tử cha cóoverflow: hidden”), không mơ hồ (“sửa linh tinh”). - Vì sao — động cơ hoặc lỗi được sửa, đặc biệt với các thay đổi hành vi có thể trông tùy tiện nếu thiếu ngữ cảnh.
- Cách migrate, nếu là breaking — một đoạn code before/after, hoặc link tới hướng dẫn migration đầy đủ và lệnh codemod.
Ví dụ một mục changelog breaking-change tốt:
## [5.0.0] - 2026-08-04
### Changed
- **BREAKING:** Prop `type` của `Button` đã được đổi tên thành `variant` để
tránh xung đột với attribute HTML gốc `type` (`button`/`submit`/`reset`).
Chạy `npx @acme/design-system-codemods rename-button-prop` để cập nhật
tự động, hoặc xem [hướng dẫn migration](./MIGRATIONS.md#v5).
```diff
- <Button type="primary">Save</Button>
+ <Button variant="primary">Save</Button>
Deprecated
- Prop
onClosecủaModalđã bị deprecated, thay bằngonOpenChange, vốn cũng báo về trạng thái open mới.onClosesẽ bị xóa trong v6.0.0 (dự kiến Q1 2027).
### Roadmap & milestone
Changelog thông báo những gì đã xảy ra; **roadmap công khai** thông báo những gì sắp tới. Các team consumer nhìn thấy "v6.0.0, dự kiến Q1 2027, sẽ xóa prop `Modal.onClose` đã deprecated và ngừng hỗ trợ React 16" có thể lên kế hoạch sprint của riêng họ thay vì bị bất ngờ bởi một major release. Roadmap thường tồn tại dưới dạng một GitHub Project board được ghim, một file `ROADMAP.md`, hoặc một mục trong trang tài liệu của design system, và được xem xét trong cùng diễn đàn với các quyết định [Governance & Contribution](/notes/design-system/11-governance-and-contribution), vì quyết định điều gì sẽ breaking tiếp theo về bản chất là một câu hỏi governance.
### Chiến lược rollback
Dù có mọi biện pháp bảo vệ, cuối cùng vẫn sẽ có một release ship một regression chỉ lộ ra dưới lưu lượng production thực tế. Design system cần một lộ trình rollback nhanh chóng, đã được diễn tập kỹ:
- **Pin-back luôn khả thi.** Vì mỗi release là một số phiên bản SemVer đã được xuất bản, bất biến (immutable), bất kỳ consumer nào cũng có thể revert ngay lập tức bằng cách ghim `package.json` về phiên bản known-good cuối cùng và cài lại — không cần hành động gì từ phía team design system. Đây là lý do việc xuất bản mọi release (kể cả patch) như một số phiên bản rời rạc, vĩnh viễn lại quan trọng: đó chính là cơ chế rollback.
- **Deprecate/unpublish phiên bản lỗi tập trung.** Với một regression nghiêm trọng (ví dụ lỗi accessibility hay lỗ hổng bảo mật), team design system có thể đánh dấu phiên bản lỗi là deprecated trên registry (`npm deprecate @acme/react-components@4.5.0 "Chứa regression nghiêm trọng, hãy upgrade lên 4.5.1"`), việc này sẽ hiện cảnh báo cho bất kỳ ai cài phiên bản đó, mà không phá vỡ lockfile hiện có.
- **Ship một patch ngay trong ngày.** Cách sửa nhanh nhất thường là một patch release mới hoàn tác thay đổi gây lỗi, xuất bản trong vòng một giờ, để những consumer đã upgrade lên phiên bản lỗi có thể tiến lên bản vá thay vì phải lùi lại.
- **Feature-flag các thay đổi rủi ro.** Với các thay đổi hành vi lớn hơn, đặt chúng sau một prop hoặc flag opt-in cho phép design system ship code trong một release mà không đổi hành vi mặc định, tách biệt "code đã tồn tại" khỏi "code đang hoạt động," giúp cả roll-forward lẫn rollback đều rẻ hơn.
Khả năng rollback nên được kiểm thử, không phải chỉ giả định — cũng giống như [CI/CD pipeline](/notes/devops/11-ci-cd) cho application code diễn tập rollback, pipeline release của design system cũng nên có một runbook "làm sao để un-ship cái này" được ghi lại và luyện tập.
### Bảng tổng quan: loại thay đổi → SemVer bump → giao tiếp cần thiết
| Loại thay đổi | SemVer bump | Giao tiếp cần thiết |
|---|---|---|
| Bug fix, không thay đổi API/visual-contract (ví dụ sửa lỗi focus-ring bị clip của `Button`) | PATCH | Mục changelog dưới `Fixed`; không cần bước migration |
| Tính năng mới, tương thích ngược (ví dụ thêm `size="xl"` cho `Button`) | MINOR | Mục changelog dưới `Added`; có thể cập nhật docs, không cần migration |
| Deprecation một API hiện có, API cũ vẫn hoạt động (ví dụ `Modal.onClose` bị deprecated, thay bằng `onOpenChange`) | MINOR | Mục changelog dưới `Deprecated`; cảnh báo runtime; hướng dẫn migration được công bố; nêu rõ ngày sẽ xóa |
| Breaking change: đổi tên/xóa prop, đổi default behavior, xóa component (ví dụ `Button.type` đổi tên thành `variant`) | MAJOR | Mục changelog dưới `Changed`/`Removed` đánh dấu `BREAKING`; hướng dẫn migration; codemod nếu là thay đổi cơ học; thông báo roadmap trước; khuyến nghị kiểm chứng qua pre-release (`rc`) |
## Best Practices
Coi changelog là một sản phẩm bàn giao (deliverable) quan trọng ngang với chính thay đổi code — một PR làm thay đổi hành vi component mà không có mục changelog nên bị fail khi review, giống như khi thiếu test vậy. Tự động hóa việc bump version và sinh changelog bất cứ khi nào có thể (Changesets, `semantic-release`) để quy trình không phụ thuộc vào việc ai đó nhớ làm đúng dưới áp lực release. Gộp các breaking change liên quan vào một major release duy nhất thay vì rải rác nhiều major liên tiếp — mỗi lần bump major version đều tốn công sức migration của mọi team consumer, nên hãy gộp lại để giảm thiểu số lần phải trả cái giá đó. Luôn ship một hướng dẫn migration và, với các thay đổi mang tính cơ học, một codemod đi kèm ngay trong major release — thông báo một breaking change mà không có lộ trình khắc phục cụ thể chỉ biến thông báo đó thành sự lo lắng. Theo dõi mức độ sử dụng thực tế của API đã deprecated (qua lint rule, telemetry, hoặc grep qua các repo nội bộ) trước khi xóa bất cứ thứ gì, vì "chúng tôi đã thông báo ba tháng trước" không đồng nghĩa với "không còn ai dùng nữa." Xuất bản một roadmap được cập nhật liên tục và xem xét lại nó trong cùng diễn đàn governance được mô tả ở [Governance & Contribution](/notes/design-system/11-governance-and-contribution), vì lập kế hoạch release là một hoạt động governance, không chỉ đơn thuần là kỹ thuật. Cuối cùng, đảm bảo thông báo release đến được với consumer qua các kênh họ thực sự theo dõi — một changelog không ai đọc thì không phải là giao tiếp; hãy kết hợp nó với các thực hành tiếp cận được nêu ở [Adoption, Community & Support](/notes/design-system/13-adoption-community-and-support).
## Tài liệu tham khảo
- [Semantic Versioning 2.0.0](https://semver.org/)
- [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
- [Changesets — versioning cho monorepo](https://github.com/changesets/changesets)
- [npm — About semantic versioning](https://docs.npmjs.com/about-semantic-versioning)
- [jscodeshift — toolkit chạy codemod](https://github.com/facebook/jscodeshift)
- [semantic-release — quản lý version tự động](https://semantic-release.gitbook.io/semantic-release/)
- [roadmap.sh — Design System](https://roadmap.sh/design-system)Part of the Design System Roadmap knowledge base.
Overview
A design system is not just another internal library — it is a dependency shared by every product team in the organization at once. When a typical backend service ships a breaking change, one team feels the pain. When a design system ships an uncommunicated breaking change, dozens of teams can feel it simultaneously, often in production, often at the worst possible time (a release freeze, a demo, an on-call rotation with nobody familiar with the design system’s internals). This is the central reason design systems need more disciplined versioning and release management than most software: the blast radius of a mistake is organization-wide, not team-wide.
Good versioning and release practice turns “the design system team changed something and broke us” into “we saw the changelog, ran the codemod, and upgraded on our own schedule.” The mechanisms that make this possible — semantic versioning, deprecation policies, changelogs, pre-release channels, and rollback plans — are the subject of this note.
Fundamentals
Why versioning discipline matters more here than elsewhere
Three properties of a design system make versioning unusually high-stakes:
- Fan-out. A single component package (say
@acme/react-components) might be installed in 50+ product repositories. A change that seems small to the design system team is multiplied by every consumer. - Visual and behavioral coupling. Design system changes often affect pixels, spacing, and interaction behavior across many screens at once — a bug is not confined to one function’s return value, it is confined to how customers perceive the whole product.
- Trust is the product. If teams stop trusting that upgrading is safe, they pin old versions forever and the design system fragments — the exact outcome it was built to prevent. Reliable versioning is what keeps adoption voluntary and continuous.
Semantic Versioning (SemVer)
The design system ecosystem has converged on Semantic Versioning as the standard scheme: version numbers look like MAJOR.MINOR.PATCH (e.g. 4.3.1), and each segment has a precise contractual meaning.
| Segment | Meaning | Design-system example |
|---|---|---|
| PATCH | Backwards-compatible bug fix, no API or visual-contract change | Fixed a visual bug where Button’s focus ring was clipped by overflow: hidden on its parent. 4.3.0 → 4.3.1 |
| MINOR | Backwards-compatible new functionality | Added a new size="xl" variant to Button, existing usages unaffected. 4.3.1 → 4.4.0 |
| MAJOR | Backwards-incompatible change | Renamed the Button prop type to variant, or changed Modal’s default closeOnOverlayClick from true to false. 4.4.0 → 5.0.0 |
The value of SemVer is that it lets a consuming team reason about upgrade risk without reading the code — a ^4.3.0 dependency range can absorb patch and minor releases automatically, while a major bump requires a deliberate, reviewed upgrade. This is why a design system must be strict about what counts as “breaking”: anything that could require a consumer to change their code, their markup, their visual regression baselines, or their accessibility tree counts as breaking, even if the underlying implementation is “just CSS.”
A design token package deserves the same discipline. Renaming color.brand.primary to color.brand.500 is a MAJOR change for the tokens package, even though no component logic changed — anyone consuming the raw token name breaks.
Pre-1.0 versions and the “0.x” caveat
Per the SemVer spec, 0.y.z is explicitly for initial development, and anything may change at any time — there is no promised stability. Many design systems intentionally stay in 0.x during their first few months while the component API is still being discovered, and treat 1.0.0 as the moment they commit to API stability. Communicate this clearly in the README so early adopters don’t assume 0.x carries the same guarantees as 1.x.
Key Concepts
Monorepo vs. multi-repo for a multi-package design system
A design system is rarely one package — a typical setup has design tokens, a React (or Vue/Web Components) component library, an icon package, and sometimes a Figma-plugin or documentation site. Where these packages live has real versioning consequences.
| Aspect | Monorepo (e.g. via Nx, Turborepo, Lerna/Changesets) | Multi-repo (separate repos per package) |
|---|---|---|
| Versioning | Can be unified (tokens, react, icons all move to 5.0.0 together) or independent per package with shared tooling | Fully independent per repo; no automatic coordination |
| Cross-package changes | Easy to land an atomic PR that updates tokens and the components that consume them together | Requires coordinated PRs across repos, easy to get out of sync |
| CI/CD | One pipeline can build, test, and publish all packages consistently | Each repo needs its own pipeline, more duplication |
| Discoverability | One place to search history, issues, and code | Contributors must know which repo owns what |
| Release noise | A change to icons can force a release of the tokens package too if versions are locked in lockstep (unless using independent versioning) | Consumers only see releases for packages they use |
| Common tooling | Changesets is the de facto standard for versioning + changelog generation in a monorepo | Manual tagging or per-repo semantic-release |
Most actively maintained design systems (e.g. Shopify Polaris, Atlassian’s Atlaskit) use a monorepo with independent per-package versioning — each package gets its own SemVer number, but they are built, tested, and released from the same repository, using tools like Changesets to track which packages changed in a given PR and to auto-generate a changelog entry per package. This gets most of the monorepo’s atomicity benefits (tokens and components can change together) without forcing every consumer to bump every package on every release.
Release strategy: cadence and channels
Cadence. There are two broad philosophies:
- Continuous / frequent small releases — every merged PR that touches a package can trigger a patch or minor release (often automated via
semantic-releaseor Changesets’ release action). This keeps changes small and easy to review, and lets fixes reach consumers within hours. - Scheduled, batched releases — changes accumulate on a release branch and go out on a cadence (weekly, bi-weekly, or per sprint). This is easier for consuming teams to plan around (“design system releases every other Tuesday”) and reduces the fatigue of upgrading multiple times a day, at the cost of slower fix delivery.
Many teams land in the middle: patch releases go out continuously (bug fixes should never be held hostage to a schedule), while minor and major releases follow a predictable, announced cadence.
Pre-release channels. Not every consumer wants stability at the same moment — some teams are happy to try bleeding-edge changes in exchange for early access or influence over the final API. Standard npm dist-tags support this:
| Channel | npm dist-tag example | Audience |
|---|---|---|
| Stable | 4.4.0 (latest) | Default for all consumers |
| Release candidate | 5.0.0-rc.1 (next) | Teams preparing to upgrade before a major GA |
| Beta | 5.0.0-beta.3 (beta) | Teams willing to test upcoming features, expect some churn |
| Alpha / canary | 5.1.0-canary.20260719 (canary) | Design system team’s own dogfooding, or adventurous internal teams; built from every merge to main |
Publishing pre-releases lets the design system team validate a breaking change against real consumer code before it becomes the default latest, which catches problems that unit tests inside the design system repo would never see.
Deprecation strategy
The single most important rule of design-system deprecation: never remove something in the same release in which you deprecate it. Deprecation and removal are two separate events, separated by a grace period long enough for every consuming team to migrate on their own schedule.
A healthy deprecation sequence looks like this:
- Announce & warn (MINOR release). The old API keeps working exactly as before, but using it now triggers a warning — a console warning at runtime (e.g.
Button’stypeprop is deprecated, usevariantinstead), a TypeScript@deprecatedJSDoc tag that lints/IDEs surface, and a changelog entry. - Grace period. A stated window (commonly one to two minor/major release cycles, or a fixed number of months) during which both the old and new API work side by side. The design system team tracks internal usage analytics where possible to see how many consumers have migrated.
- Provide a migration path. A written migration guide describing exactly what changed and why. For purely mechanical renames (a prop rename, an import path change), ship a codemod — a script (often built on jscodeshift) that rewrites consumer code automatically, turning what would be hours of manual find-and-replace into a single command like
npx @acme/design-system-codemods rename-button-prop. - Remove (MAJOR release). Only once the grace period has elapsed — and ideally once usage telemetry shows the deprecated API is close to unused — does the next major version actually delete the old API. This removal is itself a breaking change and is documented as such in the changelog.
This sequencing is what allows a design system to evolve its API without ever forcing an emergency same-day migration on 40 product teams.
Changelogs
A changelog is the primary communication surface between the design system team and every consumer, and it deserves as much care as the code itself. The Keep a Changelog format is the widely adopted convention:
- One
CHANGELOG.mdper package (or per release, if using Changesets, which auto-generates and prepends entries). - Entries grouped under a version heading and date, further grouped by category:
Added,Changed,Deprecated,Removed,Fixed,Security. - An
Unreleasedsection at the top collects changes that have merged but not yet shipped.
A good individual changelog entry answers three questions, not just one:
- What changed — precise and specific (“
Button: fixed focus ring clipping when a parent hasoverflow: hidden”), not vague (“misc fixes”). - Why — the motivation or the bug being fixed, especially for behavior changes that might look arbitrary out of context.
- How to migrate, if breaking — a code snippet showing before/after, or a link to the full migration guide and codemod command.
Example of a strong breaking-change entry:
## [5.0.0] - 2026-08-04
### Changed
- **BREAKING:** `Button`'s `type` prop has been renamed to `variant` to avoid
colliding with the native HTML `type` attribute (`button`/`submit`/`reset`).
Run `npx @acme/design-system-codemods rename-button-prop` to update
automatically, or see the [migration guide](./MIGRATIONS.md#v5).
```diff
- <Button type="primary">Save</Button>
+ <Button variant="primary">Save</Button>
Deprecated
Modal’sonCloseprop is deprecated in favor ofonOpenChange, which also reports the new open state.onClosewill be removed in v6.0.0 (tentatively Q1 2027).
### Roadmap & milestones
Changelogs communicate what already happened; a **public roadmap** communicates what is coming. Consuming teams that can see "v6.0.0, planned for Q1 2027, will remove the deprecated `Modal.onClose` prop and drop support for React 16" can plan their own sprints around it instead of being ambushed by a major release. A roadmap typically lives as a pinned GitHub Project board, a `ROADMAP.md`, or a section of the design system's documentation site, and is reviewed in the same forum as [governance and contribution](/notes/design-system/11-governance-and-contribution) decisions, since deciding what breaks next is fundamentally a governance question.
### Rollback strategy
Despite every safeguard, a release will eventually ship a regression that only shows up under real production traffic. A design system needs a fast, well-rehearsed rollback path:
- **Pin-back is always possible.** Because every release is an immutable, published SemVer version, any consumer can revert instantly by pinning `package.json` to the last known-good version and re-installing — no design-system-side action required. This is why publishing every release (even patches) as a discrete, permanent version number matters: it's the rollback mechanism.
- **Deprecate/unpublish the bad version centrally.** For a severe regression (e.g. an accessibility failure or a security issue), the design system team can mark the broken version as deprecated on the registry (`npm deprecate @acme/react-components@4.5.0 "Contains a critical regression, upgrade to 4.5.1"`), which surfaces a warning to anyone installing it, without breaking existing lockfiles.
- **Ship a same-day patch.** The fastest real fix is usually a new patch release that reverts the offending change, published within the hour, so that consumers who already upgraded to the broken version can move forward to the fix rather than backward.
- **Feature-flag risky changes.** For larger behavioral changes, gating them behind an opt-in prop or flag lets the design system ship the code in a release without changing default behavior, decoupling "the code exists" from "the code is active," which makes both roll-forward and rollback cheaper.
Rollback capability should be tested, not assumed — the same way [CI/CD pipelines](/notes/devops/11-ci-cd) for application code rehearse rollback, a design system's release pipeline should have a documented, practiced "how do we un-ship this" runbook.
### Change type to SemVer bump, at a glance
| Change type | SemVer bump | Communication needed |
|---|---|---|
| Bug fix, no API/visual-contract change (e.g. fixed `Button` focus-ring clipping) | PATCH | Changelog entry under `Fixed`; no migration steps |
| New backwards-compatible feature (e.g. added `size="xl"` to `Button`) | MINOR | Changelog entry under `Added`; optional docs update, no migration required |
| Deprecation of an existing API, old API still works (e.g. `Modal.onClose` deprecated in favor of `onOpenChange`) | MINOR | Changelog entry under `Deprecated`; runtime warning; migration guide published; removal date stated |
| Breaking change: renamed/removed prop, changed default behavior, removed component (e.g. `Button.type` renamed to `variant`) | MAJOR | Changelog entry under `Changed`/`Removed` marked `BREAKING`; migration guide; codemod if mechanical; roadmap notice ahead of time; pre-release (`rc`) validation recommended |
## Best Practices
Treat the changelog as a deliverable of equal importance to the code change itself — a PR that changes component behavior without a changelog entry should fail review, the same way it would fail without tests. Automate version bumping and changelog generation wherever possible (Changesets, `semantic-release`) so the process doesn't depend on someone remembering to do it correctly under release pressure. Bundle related breaking changes into a single major release rather than trickling out several majors in quick succession — every major version bump costs every consuming team migration effort, so batch to minimize how often that cost is paid. Always ship a migration guide and, for mechanical changes, a codemod alongside the major release itself — announcing a breaking change without a concrete path to fix it just converts the announcement into anxiety. Track real deprecated-API usage (via lint rules, telemetry, or a grep across internal repos) before removing anything, since "we announced it three months ago" is not the same as "no one is using it anymore." Publish a rolling roadmap and revisit it in the same governance forum described in [Governance & Contribution](/notes/design-system/11-governance-and-contribution), because release planning is a governance activity, not just an engineering one. Finally, make sure release announcements reach consumers through the channels they actually watch — a changelog nobody reads is not communication; pair it with the outreach practices covered in [Adoption, Community & Support](/notes/design-system/13-adoption-community-and-support).
## References
- [Semantic Versioning 2.0.0](https://semver.org/)
- [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
- [Changesets — versioning for monorepos](https://github.com/changesets/changesets)
- [npm — About semantic versioning](https://docs.npmjs.com/about-semantic-versioning)
- [jscodeshift — toolkit for running codemods](https://github.com/facebook/jscodeshift)
- [semantic-release — automated version management](https://semantic-release.gitbook.io/semantic-release/)
- [roadmap.sh — Design System](https://roadmap.sh/design-system)