Back to blog

Component APIs are contracts, not configurations

April 20, 2026

A design system team renames a prop. variant becomes intent across 12 components. The reasoning is sound: the old name was ambiguous, the new one reflects how the components actually behave. The change ships in a minor release. Four weeks later, a consuming app breaks in production. Their version of the package still expected variant. Nobody told them it changed.

This is not a versioning failure. The package version incremented. The changelog noted the change. The failure happened earlier, at the point where the team treated a prop rename as an internal cleanup rather than a published commitment. The component API was never held to the standard of a contract.

What a contract means in practice

An API treated as internal implementation means: the team that builds the component decides when and whether to change it. Consumers adapt on their schedule, or they pin an old version and live with the divergence.

An API treated as a published contract means something different. Changes are additive by default. Breaking changes are announced, versioned, and supported through a migration path. The bar for a breaking change is high, not because change is bad, but because the cost of that change is paid by every team consuming the component.

The distinction matters because props are not just configuration knobs. A size prop that accepts "sm" | "md" | "lg" is an agreement. Every consumer app that passes size="sm" has taken a dependency on that string. Renaming it to "small" is a breaking change whether or not the team calls it one. The consuming app does not care about naming conventions or internal consistency. It cares that the component still works.

Additive vs. breaking: the decision that matters

There are two kinds of API change. Additive changes extend the contract without breaking it: a new optional prop with a safe default, a new accepted value for an existing prop, a new callback that fires on a new event. No consumer breaks. Apps that do not use the new prop see no change.

Breaking changes revoke or alter a commitment the API has already made: renaming a prop, changing its accepted values, removing a prop entirely, changing the default behavior of an existing prop. Any consumer relying on the old shape breaks.

The practical discipline: when in doubt, add rather than change. A deprecated variant prop that still works alongside the new intent prop gives consuming teams time to migrate. It is less clean than removing the old prop immediately. That is the point. A clean API that breaks things in production is not a good trade.

Design systems teams often optimize for API aesthetics, preferring minimal, well-named props over backward-compatible cruft. This is the right instinct when a component lives inside one app. When the same component ships to dozens of consuming apps with different release schedules, API stability is worth more than API elegance.

Where design decisions and API decisions collide

The design team decides that size is the wrong mental model. Components should express density, not size. "sm", "md", "lg" become "compact", "default", "spacious". The reasoning is defensible. The prop name genuinely was confusing. The rename improves the mental model for anyone picking up the component for the first time.

It is also a breaking change affecting every consumer.

This is where many teams miss the distinction. Design decisions that stay inside the component, such as color, spacing values, or animation duration, are invisible to the contract. Consumers cannot take a dependency on them. Design decisions that surface in props, the vocabulary consumers use to configure behavior, are part of the contract. They cannot change without cost.

Design system leads need to catch this distinction before the rename ships. The review question is not "is this a better name?" but "do we have a migration path for every consumer who already uses the old name?" Both questions matter. Only one of them prevents production breaks.

The teams that handle this well are not more process-heavy than the ones that do not. They made one decision early: props are public API. Once that is true, a prop rename goes through the same review as any other interface change. It needs a deprecation path. It needs consumer communication. It needs a migration window. That one shift in framing catches most contract violations before they reach consuming apps. The component API is not internal. The moment it ships in a package, it belongs to everyone using it.

Ask about ReframeUI