We Use Too Many Tools to Solve Simple UI Problems
Modern frontend development has a strange problem: we have more tools than ever, yet building simple UI often feels harder, slower, and more fragile than it should be.
A button. A card. A layout. Things we solved years ago with plain HTML and CSS now require multiple libraries, configurations, and abstractions. Somewhere along the way, simplicity got buried under layers of tooling.
This article isn’t anti-tools — it’s about using the right level of complexity for the problem at hand. Let me show you what I mean.
When Simple UI Becomes Overengineered
Let’s take a very common example: a button. Just a clickable element that triggers an action.
Today, a “simple” button might involve:
- A component library (Material UI, Chakra, etc.)
- A utility-first CSS framework (Tailwind, UnoCSS)
- A design token system (style-dictionary)
- A theme provider (emotion, styled-components)
- A variant manager (class-variance-authority)
- Build-time configuration (PostCSS, CSS modules)
- Runtime styles injection
All that infrastructure… for a clickable element.
<Button
variant="primary"
size="lg"
intent="success"
radius="md"
elevation="2"
leftIcon={<SaveIcon />}
isLoading={isSubmitting}
>
Save Changes
</Button>
Don’t get me wrong — this API is clean and convenient. But behind it sits an enormous amount of abstraction. The problem isn’t the button itself — it’s how many layers exist between intent and result.
The Hidden Cost
When you use this button, you’re importing:
// What you see
import { Button } from "@ui/components";
// What you're actually loading
import { ThemeProvider } from "@emotion/react";
import { useTheme } from "./theme-context";
import { cx } from "class-variance-authority";
import { forwardRef } from "react";
import { Spinner } from "./Spinner";
import { Icon } from "./Icon";
// ... and dozens more dependencies
For a button.
Tools Are Not Free
Every tool you add to your project introduces cost:
Mental Overhead
Every abstraction is another concept developers must understand. New team members don’t just learn React — they learn your component library’s API, your CSS-in-JS solution, your design token system, and your build configuration.
Onboarding Cost
I’ve seen talented developers spend days just understanding the architecture before writing a single line of productive code. “How do I change the color of this text?” shouldn’t require reading three documentation sites.
Maintenance Cost
Tools need updates. APIs change. Breaking changes happen. That button library you added? It now requires React 18, but your app is on 17. Time to refactor.
Upgrade Risk
What happens when the tool is abandoned? Or when the team pivots to a different solution? You’re left with technical debt and migration work.
The Worst Part?
We often add tools based on assumptions, not evidence:
- “Everyone uses it” — popularity isn’t proof of necessity
- “It scales better” — but does your project need that scale?
- “We might need it later” — YAGNI (You Aren’t Gonna Need It) applies to tooling too
Most projects never reach the complexity these tools were designed for.
The UI Layer Is Not the Backend
Here’s something that took me years to internalize: UI systems and backend systems have fundamentally different needs.
Backend systems often benefit from heavy abstraction:
- Database layers isolate data access
- Service layers enforce business rules
- API layers provide consistent interfaces
These abstractions help manage real complexity: distributed systems, data consistency, security, and scale.
UI Is Different
The UI layer is inherently visual and declarative. It benefits from clarity over abstraction.
CSS already gives us:
- Layout systems (Flexbox, Grid)
- State handling (
:hover,:focus,:disabled) - Responsiveness (media queries, container queries)
- Theming (custom properties)
- Animations (transitions, keyframes)
But instead of mastering CSS, we wrap it in JavaScript and abstractions.
A Real Example
Here’s a button with plain CSS:
.btn {
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
border: none;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.btn--primary {
background: var(--color-primary);
color: white;
}
.btn--primary:hover {
background: var(--color-primary-dark);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.btn--primary:active {
transform: translateY(0);
}
.btn--primary:disabled {
background: var(--color-gray-300);
cursor: not-allowed;
transform: none;
}
And the HTML:
<button class="btn btn--primary">Save Changes</button>
This is not primitive. This is readable, debuggable, and predictable.
No build step needed. No runtime injection. No JavaScript for styling. Just CSS doing what it was designed to do.
When Tools Actually Make Sense
I’m not saying never use tools. I’m saying use them intentionally.
Tools are valuable when:
1. The Problem Repeats Many Times
If you’re building 50+ components that share behavior, a component library makes sense. But for 5 components? Probably overkill.
2. Rules Must Be Enforced Across Teams
Large teams benefit from constraints. A design system with tokens ensures consistency when dozens of developers are contributing.
3. Consistency Matters More Than Flexibility
If brand consistency is critical (think banking, healthcare), the constraints imposed by a design system are features, not bugs.
4. The System Is Designed, Not Improvised
Tools work best when you’ve already solved the problem manually and understand it deeply. If you’re still figuring out your component API, adding abstraction layers too early will hurt.
The Real Issue
The issue isn’t the tools themselves — it’s starting with tools instead of starting with understanding.
A Healthier Frontend Mindset
Here’s the mental framework I use before reaching for a new tool:
1. What Problem Are We Solving?
Be specific. “We need better styling” is vague. “We need consistent spacing across 200 components” is concrete.
2. Can Native CSS/HTML Solve This?
Modern CSS is incredibly powerful. Before adding a tool, try solving it with platform primitives. You might be surprised.
3. Will This Tool Reduce or Increase Cognitive Load?
Some tools simplify (ESLint catches bugs automatically). Others complicate (another config file to maintain). Be honest about which category your tool falls into.
4. What Happens If We Remove It in a Year?
“Reversibility” is underrated. How painful would it be to undo this decision? The more painful, the more carefully you should evaluate it.
5. Have We Tried the Simple Thing First?
Complexity should be earned through experience, not assumed upfront. Start simple. Add complexity only when you hit real limitations.
Practical Examples from Real Projects
Let me share two stories from projects I’ve worked on:
Project A: The Over-Tooled Dashboard
A client hired us to build an internal dashboard. Simple CRUD operations, data tables, forms. Nothing exotic.
The previous team had set up:
- Material UI
- Emotion for CSS-in-JS
- Styled-system for spacing utilities
- Formik + Yup for forms
- Redux for global state
- Redux-Saga for side effects
For an internal tool with 3 users.
The result? Developers spent more time fighting abstractions than building features. Bundle size was 800KB. Build time was 4 minutes.
Our solution? We rewrote it with plain React, basic CSS modules, and React Query for server state. Bundle dropped to 180KB. Build time: 30 seconds. Development velocity doubled.
Project B: The Under-Tooled Design System
A different client — a large enterprise with 50+ frontend developers across multiple teams. They had no shared components. Every team built their own buttons, inputs, modals.
The result? Wildly inconsistent UI. Designers couldn’t scale. Code duplication everywhere.
Our solution? We built a proper design system with a component library, design tokens, and Storybook for documentation. This increased complexity, but it was earned complexity. The problem was real, repeated, and costly.
What I’ve Learned
After years of building UI, here’s what I believe:
Start With the Platform
HTML and CSS are not “old” or “legacy” — they’re the foundation. Master them before abstracting them away.
Add Constraints Gradually
Don’t design for scale you don’t have. A 3-person team doesn’t need the same architecture as a 50-person team.
Optimize for Change, Not Perfection
Your first version will be wrong. Make it easy to change, not “perfect” upfront.
Sometimes, Boring Is Better
The most maintainable code I’ve written has been the most boring: straightforward HTML, vanilla CSS, minimal abstraction.
The Bottom Line
Frontend didn’t become harder because UI got more complex. It became harder because we over-solved problems that were already solved.
Better frontend isn’t about fewer tools — it’s about using tools intentionally.
Before you add that next library, ask yourself: am I solving a problem, or am I just following a trend?
Sometimes, the simplest solution is still the best one.
Code with purpose, build with intention.