A design system releases 4.3.0. The changelog says no breaking changes. Eleven of forty consuming applications report broken layouts. Review the versioning policy - what is wrong with it?
Show the full answer Hide the answer
What is actually the contract
The team defines the contract as the component's props. Consumers defined it as everything they could observe, and that is the operative definition, because it is what their code depends on.
The observable surface of a rendered component includes:
- The DOM structure - any consumer using a descendant selector, a test selector or a wrapper style depends on it.
- Class names, if they are stable enough to target, and someone always targets them.
- Computed spacing, sizing and typography. A change from 8 px to 12 px padding is not a props change and it reflows every layout tuned around it.
- CSS specificity and source order, which determine whether a consumer's override still wins.
- Rendering timing - whether content appears synchronously or after an effect - which breaks tests and measurement code.
None of this is in the props, and all of it is in the contract. A version policy that only governs the props is governing a minority of what consumers depend on.
What I would change
- Redefine the breaking-change rule in terms of observable output: "if a consumer's rendered result can change without their code changing, it is a major release". That makes visual changes major by definition, which is uncomfortable and correct.
- Publish an explicit override surface - design tokens and documented slots - and state that anything else, including DOM structure and class names, is private and may change at any time. A contract that forbids nothing is a contract that promises everything.
- Visual regression testing in the design system's own pipeline, across a sample of real consumer compositions. This is the control that would have caught all eleven, and it has been standard practice in published design-system engineering since the mid-2010s.
- Ship codemods with major releases. If upgrading is mechanical, major versions stop being frightening, which is what makes the honest policy affordable.
What I would leave alone
The shared component library itself, and the expectation that consumers stay current. The diagnosis will tempt someone to propose letting each application pin its own version indefinitely, which converts one upgrade problem into forty divergent forks - the failure the design system was created to prevent.
How I would argue this in the review
Not as "the team broke semantic versioning". They followed it, against the surface they had declared. The finding is that the declared surface does not match the depended-upon surface, which is a specification problem with a specific, testable remedy. The ask is the visual regression suite and the rewritten policy, and the evidence is eleven applications.
When this is the wrong policy
For an internal library with two consumers in the same repository, this machinery costs more than the breakages. The build catches the compile-time part and a person notices the visual part within a day. Choose to formalise the observable contract at the point where you no longer know who your consumers are - which for a design system is early, and for an internal utility may be never. In practice the line sits around 5 consuming applications, or the first consumer outside the team that publishes it, whichever comes first.