Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

The Hidden Cost of Shared Components

A shared component often starts with a sensible observation: these two screens look similar. Someone extracts a component. A third screen adopts it. Then come compact, showTotals, isCheckout, and useLegacySpacing. A cha

A shared component often starts with a sensible observation: these two screens look similar.

Someone extracts a component. A third screen adopts it. Then come compact, showTotals, isCheckout, and useLegacySpacing. A change for one consumer breaks another. The component has saved lines of code while creating a negotiation every time the product changes.

Reuse has a cost. The interesting question is whether the shared behavior is valuable enough to pay for that cost.

Similar markup does not imply a shared concept

Imagine a product card in search results and a line item in checkout.

Both display an image, title, and price. Search needs discovery: ratings, availability, promotional labels. Checkout needs confirmation: selected options, quantity, tax treatment, and purchase constraints.

They look related, but they change for different reasons.

Combining them into one ProductCard can make each team depend on decisions made for the other. The abstraction becomes a container for exceptions instead of a stable model.

A little duplicated markup may be cheaper than a shared policy neither team can evolve independently.

Every shared component creates an API

A reusable component is a product with consumers.

Its props become an interface. Its accessibility behavior becomes something consumers rely on. Its spacing, loading states, and interaction details can become implicit contracts even when nobody documented them.

Changes now need compatibility decisions. Who owns migration work? Can consumers upgrade separately? How do maintainers discover which combinations are used?

In a monorepo, updating every call site in one commit can reduce migration friction. It does not remove the need to verify that the new behavior still makes sense for each consumer.

The cost is often less about writing the abstraction and more about maintaining agreement around it.

Share stable behavior before feature policy

Buttons, form controls, focus management, and typography often have a strong reason to be shared: consistency and accessibility are deliberate product requirements.

Business workflows deserve more caution.

A shared Dialog can own focus restoration and keyboard interaction. A CancelSubscriptionDialog owns a business decision that may vary between products, plans, and teams.

Keeping these layers separate lets the platform maintain interaction quality while feature teams retain control over their workflows.

For example:

<Dialog title="Cancel subscription" onClose={close}>
  <CancellationSummary subscription={subscription} />
  <CancellationActions onConfirm={cancelSubscription} />
</Dialog>

The dialog supplies common interaction behavior. The feature supplies the content and cancellation policy. The example assumes Dialog implements and tests its accessibility contract; composition alone does not provide that behavior.

Watch for the wrong kind of flexibility

A growing prop list is a prompt to inspect the abstraction, not proof that it is wrong.

Ask what each option represents. A size variant may describe a coherent design system decision. isCheckout usually exposes a consumer's identity inside the shared layer.

When a component branches on which feature is using it, dependency direction is starting to reverse. The shared layer now knows about the features it was supposed to support generically.

Slots and composition can help when consumers vary in structure. They do not rescue an abstraction that combines unrelated responsibilities.

Sometimes the cleanest API is two components with clear purposes.

Extraction should follow evidence

Before moving code into a shared package, answer four questions:

  1. Do consumers need the same behavior, or merely similar visuals?
  2. Are they likely to change together?
  3. Who owns the contract and migration work?
  4. What happens when their requirements diverge?

Start locally when those answers are unclear. Compare implementations after real requirements emerge. Extract the stable intersection rather than anticipating every possible variation.

There are good reasons to standardize early, especially accessibility and brand foundations. Even then, make the responsibility explicit and give the shared layer an owner.

The best shared components reduce both implementation effort and decision effort. If reuse repeatedly forces unrelated teams to coordinate, count that coordination as part of the abstraction's cost.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.