Versioning and Changelogs for a UI Library — How to Do It Without Embarrassing Yourself

Versioning is a contract with your consumers. Break the contract silently and they'll stop trusting your library. Break it loudly — with a clear changelog and a predictable version number — and they'll upgrade with confidence. Here's how to do it right from the first release.
The first time I pushed a breaking change without bumping the major version, I found out about it through a frustrated message from someone whose production build had broken overnight. The fix was a one-line prop rename. The damage to trust took longer to repair.
Versioning feels like admin. It's actually communication. Done well, it tells your consumers exactly what changed, what broke, and whether they need to act. Done badly, it's a surprise they find out about in production.
Semantic versioning in plain English
Semantic versioning is a three-number system: MAJOR.MINOR.PATCH. Each number has a specific meaning that your consumers rely on.
PATCH — 0.1.0 → 0.1.1 A bug fix. Behaviour that was broken now works correctly. Nothing about the public API changed. Consumers can upgrade without reading the changelog.
MINOR — 0.1.0 → 0.2.0 New functionality that doesn't break anything existing. A new component. A new optional prop. A new variant. Consumers can upgrade safely — nothing they're already using has changed.
MAJOR — 0.1.0 → 1.0.0, 1.0.0 → 2.0.0 A breaking change. Something in the public API changed in a way that requires consumers to update their code. A prop was renamed or removed. A component's behaviour changed in a way that affects existing usage. Consumers must read the migration guide before upgrading.
Here's what that looks like in practice for a UI library:
When in doubt, ask: "Does a consumer need to change their code after upgrading?" If yes, it's a MAJOR bump.
The pre-1.0.0 exception
Before 1.0.0, the rules are slightly different. A 0.x.x version signals that the library is not yet stable — the API can change between minor versions without technically violating semver.
We're starting this library at 0.1.0 deliberately. While we're building out the component set, the API will evolve. Once all 40 components are published, documented, and tested, we'll cut 1.0.0 — a signal to consumers that the API is stable and the contract is in effect.
Don't rush to 1.0.0. It's a commitment, not a milestone.
Setting up CHANGELOG.md
Create CHANGELOG.md in the project root right now, before there's anything to put in it. The habit of maintaining it from the start is more important than its contents at this stage.
We follow the Keep a Changelog format:
The format uses five section types under each version:
Added — new features, new components, new props Changed — changes to existing functionality that don't break the API Deprecated — features that will be removed in a future major version Removed — features removed in this release Fixed — bug fixes
Every time you publish a new version, move the [Unreleased] items under a new version heading and add the date. Keep [Unreleased] at the top as a running list of what's changed since the last release.
Bumping versions with npm version
Never edit the version field in package.json by hand. Use the npm CLI — it updates the version and creates a git tag automatically:
For a pre-release:
After bumping, build and publish:
For a pre-release, tag it so consumers don't install it by accident:
Consumers who want the stable version run npm install @your-username/ui-lib and get the latest stable. Consumers who want to test the beta run npm install @your-username/ui-lib@beta.
What a good release looks like
Here's an example changelog entry for a minor release that adds two new components:
Short, specific, and written for the consumer — not for the developer. The reader should be able to scan this and know immediately whether the release affects them.
The versioning checklist
Run through this before every release:
- All changes since the last version are listed under
[Unreleased]inCHANGELOG.md - The correct version bump type has been identified (patch / minor / major)
npm version [patch|minor|major]has been run — not a manual edit- The build has been run with
npm run buildafter the version bump npm pack --dry-runconfirms onlydist/is in the package- The
[Unreleased]section has been moved to a new versioned heading with today's date - For a breaking change: a migration guide exists in the changelog or a linked document
Up next: Building an Input component with validation states and accessibility built in — the second primitive, and the one that introduces ref forwarding, error states, and the label connection pattern we'll use across every form field.
Related: How to handle breaking changes in a public React UI library — once consumers are depending on your library, breaking changes need a process. This article at the end of the series covers the full deprecation workflow.
Muhammad Athar is the founder of DesignDev.io and the engineer behind everything you read here. He writes about the decisions behind building real products — the components, the architecture, and the tradeoffs that don't make it into the tutorial.
Photo by Kelly Sikkema on Unsplash

