DEV Community

abhilashlr
abhilashlr

Posted on

Stop Adding React Components Just Because You “Like” Them

A few days ago, we had an interesting discussion in our codebase.

Some developers wanted to introduce a new Heading component.

Something like:

<Heading level={3}>Profile Settings</Heading>
Enter fullscreen mode Exit fullscreen mode

At first glance, it looks harmless.

But there was one problem.

We already could do this:

<h3>Profile Settings</h3>
Enter fullscreen mode Exit fullscreen mode

And we also could do this using our existing Text component we had built for creating different variants of a text:

<Text as="h3">Profile Settings</Text>
Enter fullscreen mode Exit fullscreen mode

That immediately raised a bigger question:

Why are we introducing a new abstraction when the problem is already solved?

The answer I got was:

“I like that we have a separate heading component.”

That answer stuck with me.

Not because it was wrong.

But because it exposed something many teams struggle with:

How do we make architectural decisions in code without letting personal preference drive them?


The Problem With Preference-Driven Components

Frontend teams love abstractions. Sometimes too much!

We build wrappers around wrappers around wrappers until the original HTML becomes unrecognizable.

A lot of the time, the justification is vague:

  • “It looks cleaner”
  • “Feels more consistent”
  • “I like it”

Those reasons are understandable. But they’re weak engineering reasons.

Because every new component adds cost:

  • Documentation
  • Maintenance
  • Testing
  • Migration complexity
  • API overlap
  • Team confusion

A shared codebase isn’t your personal sandbox.

Every abstraction becomes part of the team’s long-term maintenance burden. That burden needs justification.


HTML Is Already a Great API

This is something we forget.

  • HTML is not primitive. *

It’s already a carefully designed abstraction.

Take headings:

<h1>Dashboard</h1>
<h2>Analytics</h2>
<h3>Revenue</h3>
Enter fullscreen mode Exit fullscreen mode

This already gives us:

  • semantic hierarchy
  • accessibility structure
  • screen reader support
  • SEO context
  • document outline

What does this improve by doing?

<Heading level={1}>Dashboard</Heading>
<Heading level={2}>Analytics</Heading>
<Heading level={3}>Revenue</Heading>
Enter fullscreen mode Exit fullscreen mode

In many cases: Nothing.

It’s just different syntax.

And changing syntax without changing behavior is rarely a useful abstraction.


A Rule That Changed How I Think About Components

Here’s the simplest rule I follow now:

Don’t abstract syntax. Abstract behavior.

This one rule kills a lot of unnecessary debates.

Bad abstraction

<Heading level={3}>Title</Heading>
Enter fullscreen mode Exit fullscreen mode

Why?

Because it mostly mirrors native HTML.


Useful abstraction

<Text variant="muted" size="small" truncate>
  Description
</Text>
Enter fullscreen mode Exit fullscreen mode

Why?

Because it adds real behavior:

  • design token mapping
  • truncation
  • theme consistency

That’s value.

That’s worth abstracting.


Not All Components Are Equal

Let’s compare two examples.

Heading

<Heading level={2}>Billing</Heading>
Enter fullscreen mode Exit fullscreen mode

Usually just renders:

<h2>Billing</h2>
Enter fullscreen mode Exit fullscreen mode

No added capability.

Mostly syntactic sugar.


Text

<Text variant="caption">Last updated 2 mins ago</Text>
Enter fullscreen mode Exit fullscreen mode

This can centralize:

  • font sizes
  • line heights
  • weights
  • spacing
  • responsive behavior

That's a legitimate design-system concern.

Because HTML doesn't solve typography tokens.

CSS/design systems do.

That's where Text makes sense.


One Concept, One API

One of the easiest ways to create confusion in a codebase is giving developers multiple ways to do the same thing.

Bad:

<Heading level={3}>Profile</Heading>
<Text as="h3">Profile</Text>
<h3>Profile</h3>
Enter fullscreen mode Exit fullscreen mode

Three options.

Same output.

Now every PR becomes a style debate.

Good systems reduce choices.

A useful principle:

One concept, one API.

If HTML already owns semantics, let it own semantics.

If Text owns typography, let it own typography.

Clear separation.

Clear ownership.


Better Composition Wins

Instead of this:

<Heading level={2}>Billing Settings</Heading>
Enter fullscreen mode Exit fullscreen mode

Prefer:

<h2>
  <Text variant="secondary">Billing Settings</Text>
</h2>
Enter fullscreen mode Exit fullscreen mode

This gives:

  • native semantics
  • reusable typography tokens
  • clear ownership of responsibility

No overlap.

No ambiguity.

Composition over abstraction.


Questions I Ask Before Adding a Component

Before adding any new component, I ask:

1. What problem does this solve?

Not:

“Looks cleaner.” or
“I like it this way.”

But:

“Enforces accessible hierarchy.”

That’s measurable.


2. Does HTML already solve it?

If yes:

Why are we replacing it?


3. Does it reduce complexity?

Or does it just move it?


4. Does it introduce overlap?

Overlapping APIs create inconsistent code.


5. Does it add behavior?

If not, it probably shouldn’t exist.


Final Thought

Not every repeated pattern deserves a component.

Not every native element needs a React wrapper.

And not every preference deserves to become a shared abstraction.

The next time someone says:

“Let's make a component for this. And I like it this way”

Ask:

Is this abstraction creating new capability, or just new syntax?

That one question can save your codebase from years of unnecessary complexity.

Because in frontend systems:

every abstraction is a liability until it proves otherwise.

Top comments (0)