Don't Design Components. Discover Them.

Recently, while working on our Design System, I realized that most of our discussions weren’t really about implementation—they were about abstraction.

It started with what seemed like a straightforward request from design: implement a reusable Progress Tile component.

The design library presented it as a single component with four layout variations. At first glance, the implementation looked simple enough. We could build a configurable component with a handful of variants and expose enough props to support every layout.

But the more we explored the design, the more uncomfortable it became.

That experience led me to two important questions.

1. Configuration or Composition?

The easiest way to implement the Progress Tile would have been to create a highly configurable component.

Internally, it contained a Card, Progress, Button, Badge, images, labels, descriptions, and other elements. Every new variation introduced another configuration option.

Eventually, the API would start looking something like this:

1
2
3
4
5
6
7
8
9
<ProgressTile
cardProps={...}
progressProps={...}
buttonProps={...}
badgeProps={...}
image={...}
description={...}
...
/>

At first, this feels convenient.

Over time, however, the wrapper becomes tightly coupled to all of its children. Every time a child component evolves, the wrapper must expose another prop or another variant. The abstraction becomes harder to understand than the UI it was trying to simplify.

This reminded us of a lesson we’d already learned from some of our page components. They started simple, but gradually accumulated more and more configuration until they became difficult to evolve.

Instead of making configuration our default, we’ve adopted a different principle:

Prefer composition over configuration.

This doesn’t mean configuration is bad.

Configuration works well when a component has a stable responsibility and well-understood variations. Components like Button, Progress, or Badge are excellent examples.

Composition, however, is usually a better default while the design is still evolving.

A good example is a Dialog. We can provide a composable foundation:

1
2
3
4
5
<Dialog>
<Dialog.Header />
<Dialog.Body />
<Dialog.Footer />
</Dialog>

and then build opinionated components such as SimpleDialog or ConfirmationDialog on top of it.

Composition provides flexibility, while reusable components emerge naturally once the abstraction has proven itself.

2. Is It Actually a Component?

As we continued reviewing the Progress Tile, another question emerged.

Was it actually a reusable component?

The more we compared the four layouts, the harder it became to identify a stable structure.

Some layouts contained images, others didn’t.

Some placed labels above the progress bar, others beside it.

Descriptions appeared in different positions.

Actions moved around.

The only thing shared across every variation was a progress bar.

That wasn’t enough to define a reusable engineering abstraction.

Instead of asking:

“How should we implement this component?”

we found ourselves asking:

“Is there actually a component here to implement?”

Perhaps what design had created wasn’t an engineering component at all.

Perhaps it was a design pattern.

That distinction changed the entire discussion.

A design pattern absolutely belongs in the design system because it promotes consistency across products.

However, a design pattern doesn’t necessarily need to become a single React or Angular component.

Sometimes documenting a recommended composition of existing primitives is a better engineering abstraction than introducing another configurable component.

The Missing Review

This experience also highlighted something else.

Many teams have design reviews.

Many teams have code reviews.

But very few teams have abstraction reviews.

Someone needs to bridge the gap between design and engineering.

Their responsibility isn’t simply to translate Figma into code.

Their responsibility is to decide how a design should be represented in the design system.

When reviewing a proposed component, they might ask:

  • Is this a token, a primitive, a composite component, a pattern, or simply a business feature?

  • Does it have a clear, single responsibility?

  • Does it have a stable structure?

  • Does it encapsulate meaningful behavior?

  • Will its API remain stable as new use cases appear?

These questions are often more important than discussing props or implementation details.

Components Are Discovered, Not Designed

The biggest lesson I took away from this experience is that the hardest part of building a Design System isn’t implementing components.

It’s discovering the right abstractions.

Good components usually have three characteristics:

  • A clear responsibility.

  • A stable structure.

  • Well-defined behavior.

If those qualities don’t exist yet, forcing everything into a configurable component is usually solving the wrong problem.

Instead, start with smaller primitives, document patterns where appropriate, and allow reusable components to emerge as the design matures.

In other words:

Don’t design components. Discover them.