🎧 Listen to this article: हिंदी · English · தமிழ் · తెలుగు · ಕನ್ನಡ · മലയാളം · ଓଡ଼ିଆ · 日本語 · 中文
The Heading That Started It All
There's a conversation happening in codebases everywhere right now. Someone proposes a new React component. It seems harmless. But then someone else asks: do we really need this? The answer often surprises people.
In an article posted on July 14, 2026, a developer on DEV Community described an architectural discussion from their codebase. A few colleagues wanted to introduce a new Heading component. The code would look like this:
<Heading level={3}>Profile Settings</Heading>
It's clean. It's semantic. It feels organized. But there was a problem.
We Already Have Three Solutions
The team could already write:
<h3>Profile Settings</h3>
And they could also use an existing Text component built for exactly this kind of flexibility:
<Text as="h3">Profile Settings</Text>
So why add a fourth way? When asked, the answer was honest: "I like that we have a separate heading component."
That's a human reason. It's not a bad reason. But it's not an engineering reason.
Personal Preference Isn't a Design Principle
Frontend teams love abstractions. Sometimes a little too much. Wrappers get wrapped. Wrappers around those get wrapped again. The original HTML becomes unrecognizable.
The justifications sound familiar: "It looks cleaner." "Feels more consistent." "I like it."
These are understandable. They're relatable. But they're weak engineering justifications. Here's why: every new component costs something.
- Documentation to write
- Code to maintain
- Tests to keep current
- New developers to train
- Migration paths when things change
- API overlap that confuses the team
A shared codebase isn't anyone's personal sandbox. Every abstraction becomes part of your team's long-term burden. That burden deserves justification stronger than preference.
HTML Is Already a Carefully Designed API
We forget this sometimes. HTML isn't primitive. It's not crude. It's a thoughtfully engineered abstraction you can build on top of—but not always replace.
Take headings. The native hierarchy already gives you:
- Semantic meaning (h1 is more important than h3)
- Accessibility support built in
- Screen reader compatibility
- SEO context
- Document outline structure
None of that is a side effect. It's engineered into the standard.
When you wrap it in a custom component like this:
<Heading level={1}>Dashboard</Heading>
<Heading level={2}>Analytics</Heading>
<Heading level={3}>Revenue</Heading>
You're not improving the output in most cases. You're just changing the syntax. And changing syntax without changing behavior is rarely worth the cost.
One Rule That Changes Everything
Here's the principle that should guide these decisions: Don't abstract syntax. Abstract behavior.
That's it. That one distinction kills most of the unnecessary debates.
A bad abstraction mirrors native HTML too closely:
<Heading level={3}>Title</Heading>
Why is this bad? Because it mostly mirrors h3. It adds nothing new.
A good abstraction adds real behavior:
<Text variant="muted" size="small" truncate>
Description
</Text>
This centralizes:
- Design token mapping
- Text truncation logic
- Theme consistency
- Responsive behavior
That's value. That's worth the abstraction cost.
One Concept Should Own One API
Confusion spreads fast in codebases. Here's a perfect breeding ground:
<Heading level={3}>Profile</Heading>
<Text as="h3">Profile</Text>
<h3>Profile</h3>
Three ways to write the same thing. Now every pull request becomes a style debate. "Should we use Heading or Text or native h3?" Nobody really knows.
Good systems reduce choices. A useful principle: One concept, one API. If HTML already handles semantics, let it own semantics. If your Text component owns typography, let it own typography. Clear separation. Clear ownership.
Composition Beats Abstraction
Instead of creating a wrapper for every combination of features, prefer composition.
Avoid this:
<Heading level={2}>Billing Settings</Heading>
Prefer this:
<h2>
<Text variant="secondary">
Billing Settings
</Text>
</h2>
You get:
- Native HTML semantics (h2 still means what h2 means)
- Reusable typography tokens (Text variant handles styling)
- Clear responsibility (h2 for structure, Text for appearance)
- No overlapping APIs
- No ambiguity
Composition is more flexible and adds less confusion than another layer of abstraction.
Five Questions Before You Build
Before proposing a new component, ask yourself:
1. What problem does this actually solve? Not "looks cleaner" or "I like it this way." Something measurable, like "enforces accessible hierarchy" or "prevents font size inconsistency."
2. Does HTML already solve it? If yes: Why would you replace it? That burden needs serious justification.
3. Does it reduce complexity? Or does it just move complexity around and hide it inside a component?
4. Does it introduce API overlap? Overlapping ways to do the same thing create inconsistent codebases. That's a cost, not a feature.
5. Does it add behavior? If the component just changes syntax without adding behavior, it probably shouldn't exist.
Conclusion
Not every repeated pattern deserves a component. Not every HTML element needs a React wrapper. And not every preference deserves to become a shared abstraction. The question that matters: Is this abstraction creating new capability, or just new syntax? Answer that honestly, and you'll protect your codebase from years of complexity that nobody actually needs.
Merits
- Provides measurable criteria for deciding when components solve real problems
- Preserves the accessibility and semantic value of native HTML elements
- Reduces team confusion caused by multiple ways to accomplish the same task
- Focuses component creation on adding new behavior instead of just changing syntax
- Encourages composition, which is more flexible than wrapper abstractions
- Helps prevent codebases from becoming buried under layers of unnecessary components
Demerits
- Developers who prefer building custom abstractions may find this approach limiting
- Codebases that already have many custom components face significant refactoring work
- Determining what counts as "behavior" versus "syntax" requires ongoing team discussion and agreement
- Composition patterns sometimes feel less organized than hierarchical component structures
Caution
This article explains architectural principles from a published source. The code examples are illustrative and use placeholder JSX syntax. Before applying these principles to your own codebase, verify the claims against the original source and adapt the guidance to your team's specific context and existing patterns. Every team's situation is different; what works for one may need adjustment for another. Always discuss architectural decisions with your team rather than applying rules unilaterally.
Frequently asked questions
When should I create a custom component instead of using HTML? — Create components when they add behavior like design tokens, responsive logic, or centralized styling. If the component only changes syntax without adding logic, native HTML or composition usually works better.
How do I tell the difference between good and bad abstractions? — Ask: Does this component solve a problem that HTML doesn't already solve? Does it centralize behavior or logic? If yes, it's probably good. If it just wraps HTML without adding capability, it's probably unnecessary.
What if my team already has too many custom components? — Start by identifying which components add actual behavior and which just wrap HTML syntax. Keep the ones solving real problems. For syntax-only wrappers, gradually migrate to native HTML or composition.
Why is composition better than wrapper components? — Composition preserves native HTML semantics, reuses design tokens clearly, and maintains separation of concerns. It's also more flexible because you combine small focused pieces rather than being locked into what a wrapper allows.
What about design system consistency? — Design systems should define behavior and design tokens (colors, fonts, spacing) rather than create wrappers. Let developers compose those tokens with native elements or minimal abstractions.
How do I explain this approach to my team? — Frame it as a decision framework for future components, not criticism of existing ones. Focus on the costs every component carries: documentation, testing, and maintenance. Agree on what kind of behavior justifies adding a new component.
Tags
#react #frontend #architecture #components #webdev #designsystems #abstraction #codemaintenance


Responses
Sign in to leave a response.