Observable Component Contract
also called Rendered Surface Contract, Implicit Component API
The full set of things a consumer can depend on in a shared component - DOM structure, class names, computed spacing, specificity and render timing - as distinct from its declared props, which is the only part most version policies govern.
A design system publishes 4.3.0. The changelog says no breaking changes; no prop was added, removed or changed. Eleven of forty consuming applications report broken layouts within a day.
The team followed semantic versioning honestly against the surface they had declared. The consumers had been depending on a much larger surface, and it is the consumers' definition that decides whether a release breaks them.
Why it matters
A version policy exists to let consumers upgrade without reading the diff. If it governs a minority of what consumers depend on, it provides confidence without providing safety - which is worse than having no policy, because teams stop reviewing minor upgrades.
The observable surface of a rendered component includes at least:
- DOM structure, depended on by every descendant selector, test selector and wrapper style.
- 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 reflows every layout tuned around it and is not a prop change - a 4 px shift is enough to break 11 of 40 consuming applications.
- CSS specificity and source order, which decide whether a consumer's override still wins.
- Render timing - synchronous versus after an effect - which breaks tests and any code measuring the DOM.
Implementation patterns
- Define the breaking-change rule in terms of output: if a consumer's rendered result can change without their code changing, it is a major release. This makes visual changes major by definition, which is uncomfortable and correct.
- Publish an explicit override surface - design tokens and documented slots - and declare everything else private and subject to change. A contract that forbids nothing promises everything.
- Visual regression tests across real consumer compositions in the design system's own pipeline, not just isolated component snapshots. Consumers break in composition.
- Ship codemods with major releases. Honest versioning is only affordable when upgrading is mechanical; otherwise the policy is abandoned under schedule pressure.
- Make class names unstable on purpose - hashed or generated - so the private parts cannot be depended on accidentally.
- Keep a canary consumer: one real application upgraded automatically on every pre-release, with its visual diff reviewed.
Industry example
This is the standard operating model of mature published design systems, and visual regression testing across consumer compositions has been documented practice in design-system engineering since roughly 2015, when the first large public design systems began publishing their release engineering. The recurring public lesson is the same: the systems that upgrade smoothly are the ones that shipped codemods and treated visual output as part of the API, and the ones that stall are those where each consuming application pinned a version and diverged.
The failure has a recognisable estate-level signature: forty applications spread across four major versions, a library team backporting fixes to all of them, and a "design system" that no longer produces a consistent design.
Failure scenarios
- Spacing or typography token changes shipped as minor releases, reflowing consumer layouts.
- DOM restructuring for accessibility or performance that invalidates consumer selectors and test IDs.
- Specificity changes from a refactor of the stylesheet, so overrides silently stop applying - no error, wrong appearance.
- Timing changes, where content that used to render synchronously now appears after an effect, breaking measurement code and tests.
- Consumers pinning indefinitely in response, producing divergence - the exact failure the design system was created to prevent.
- A major-version policy with no codemods, so majors become events nobody schedules and the library effectively freezes.
Trade-offs
| Choose | Gains | Pays |
|---|---|---|
| Output-based contract | Consumers can upgrade minors blindly | More major releases; codemods and visual tests to maintain |
| Props-only contract | Simple to state; fewer majors | Breakage on minor upgrades; consumers stop trusting versions |
| No contract, one rolling version | No versioning overhead at all | Only works inside one repository with all consumers present |
The real cost of the honest policy is release cadence: visual polish becomes a major-version activity, which slows it down. Codemods and a canary consumer are what buy that back.
When not to use it
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.
The formalisation earns its cost at the point where you no longer know who your consumers are - in practice around 5 consuming applications, or the first consumer outside the team that publishes it, whichever comes first. And if every consumer lives in one repository on one rolling version, prefer that: the contract problem is replaced by a build that proves the change works everywhere before it merges.
Interview question
Q: You own a component library used by eight product teams. You want to change the default spacing scale because the design has genuinely improved. Walk me through how you ship it.
What a strong answer covers: classifying it as breaking under an output-based rule, because consumer layouts change without consumer code changing · a major release with a codemod where the change is mechanical, and a documented migration where it is not · visual regression across real consumer compositions before release, with the diffs shared with each team · a canary consumer upgraded first · an overlap period where both scales are available behind a token so teams can migrate incrementally rather than in a single day · and the explicit conversation with product owners, since eight teams spending a day each is a real cost that should be decided rather than imposed.
Quick check
Quiz: A design system release changes no props and breaks consumer layouts. Was semantic versioning violated? Not against the declared surface - but the declared surface was wrong. The operative contract is whatever consumers can observe, so the policy, not the release, is the defect.
Flashcard: What single rule makes a component version policy honest? — If a consumer's rendered result can change without their code changing, it is a major release. Codemods are what make that affordable.