← back to Blog
Professional

When two similar components should stay separate

2 min read English

Two cards can look related and still need different contracts.

In yesid.dev-design, the recorded requirements for Transit and yesid.dev make that difference concrete. Transit expects a flat card without a shadow or highlighted edge. yesid.dev keeps a bevel and a hover shadow. Both belong to the same visual family. Their requirements still disagree.

Putting both into a shared package would be easy if the package were allowed to ask which product was calling it. That is exactly the dependency the project's rules exclude. Once shared code knows every product by name, each new exception adds another reason to edit the foundation.

The boundary I chose is smaller. The package owns the common control or surface. A product-owned adapter or style owns the part that differs. Each product keeps the checks that explain why its exception exists.

This accepts some duplication. It also gives that duplication a reason and a home.

The question is whether two pieces share the same responsibility. Their current appearance is one clue, but it does not settle what they do, who controls their state or which changes they should receive together.

A collapsible section is another example. It may have a heading, a chevron and hidden content in both products. Yet the products can differ in persistence, header composition and how closed content behaves. Shared lower-level controls are useful there. Promoting the entire section would also promote decisions that still belong to the product.

The repository uses a rule of three for composed patterns: three independent consumers must need the same contract before promotion. That is a constraint on this project, not a universal formula for every team. It forces a useful pause before treating resemblance as a stable abstraction.

There is a release consequence too. Once something becomes shared, a change has several audiences. Each product adopts an exact version and reviews its own behaviour. A passing gallery example cannot answer every question on a real product page.

I find this a useful way to make reuse more concrete. What repeats? What differs? Which differences are intentional? Who gets to decide when those differences change?

The answer may be a shared component. It may also be a small common primitive with a local wrapper, or two separate implementations whose responsibilities are still evolving. The useful result is a boundary that can be explained and checked.

Read the project story

Need a system that stays editable?

View Services Start a Project