You probably don’t have a hard UI problem.
You have an over-engineering problem.
If a simple settings page, table, or form takes days instead of hours, the issue isn’t React, Next.js, or Tailwind.
It’s how you’re approaching UI engineering.
This is a practical guide to building React UI in 1/10th of the time—without trashing quality, accessibility, or performance.
How devs over-engineer UI (without noticing)
Most teams don’t ship slow because the UI is “complex”.
They ship slow because they:
- Design a component framework before they design a single screen.
- Build “reusable” abstractions for UI that’s used once.
- Wrap basic HTML in five layers of generic wrappers.
- Treat every button like it needs to survive a Fortune 500 design system review.
You’ve seen patterns like:
BaseButton→AppButton→PrimaryButton→SubmitButtonLayout→PageLayout→DashboardLayout→DashboardSettingsLayout
All to render a <button> and a div with some padding.
On top of this, Next.js gets dragged into it:
- Everything is a client component “just in case”.
- Shared “UI abstractions” leak into data fetching.
- “Reusable” Next.js components become impossible to change without breaking half the app.
That’s not fast UI development. That’s architecture cosplay.
The hidden costs of over-engineered UI
Over-engineering feels smart until you look at the bill.
1. Cognitive overhead
Every extra abstraction is another thing a new engineer has to learn:
- What does
SmartFormactually do? - Why do we have
AppTable,DataTable, andSmartTable? - Which
Buttonis the “right” one?
Your developer productivity tanks because nobody can predict behavior without diving into the source.
2. Refactor friction
When you want to change the look of a single screen:
- You’re scared to touch
BaseButtonbecause it’s used 400+ times. - You’re scared to tweak the layout component because five routes share it.
- A visual change becomes a breaking change.
Abstractions that were meant to “save time” now block progress.
3. Runtime + performance tax
Over-engineering also hits the runtime:
- Deep component trees for trivial UI.
- Multiple context providers wrapping everything.
- Client-only Next.js components doing work that could’ve been server-rendered.
- Styling layers stacked on top of Tailwind UI instead of using it directly.
You end up with a slow, fragile React UI that took longer to build and is harder to change.
The 1/10th-time UI principles
Here’s the mindset shift that actually works in practice.
Goal: ship a boring screen in hours, not days — without wrecking maintainability.
These are the principles I use now.
Principle 1: Start from the screen, not the abstraction
Build one screen first.
- Hard-code what you need.
- Use raw HTML + Tailwind + tiny helpers.
- Don’t extract “reusable” components yet.
Only after the screen works and feels right do you look for patterns worth extracting.
Principle 2: The three-use-case rule
Don’t build a reusable abstraction until you have three real use cases.
- One use case = duplication.
- Two use cases = probably coincidence.
- Three use cases = actual pattern.
Until you hit three, keep things local:
// app/settings/notifications/page.tsx
function NotificationsPage() {
return (
<section className="space-y-6">
<header>
<h1 className="text-lg font-semibold">Notifications</h1>
<p className="text-sm text-slate-500">Control how we notify you.</p>
</header>
{/* local, boring UI – not a “NotificationPreferencesForm” library */}
<form className="space-y-4">{/* ... */}</form>
</section>
);
}When you finally extract something, you’re extracting a real pattern, not a guess.
Principle 3: Composition over configuration
If a component needs a 15‑prop API to do its job, it’s the wrong abstraction.
Prefer this:
<Card>
<CardHeader>
<CardTitle>Usage</CardTitle>
<CardActions>
<ExportButton />
</CardActions>
</CardHeader>
<CardBody>
<UsageChart />
</CardBody>
</Card>Over this:
<StatsCard
title="Usage"
showExportButton
exportVariant="ghost"
chartType="area"
padding="md"
radius="lg"
elevation="low"
/>Composition:
- Keeps React UI closer to normal JSX.
- Lets you spot layout issues at a glance.
- Makes it easier to delete or swap pieces.
Principle 4: Tailwind UI as the primary API
Stop building theme engines unless you actually need them.
Use Tailwind UI classes as your primary styling surface:
classNameis your “variant” API.data-*attributes communicate state.- Design tokens live in Tailwind config / CSS custom properties.
Example:
<button
data-variant="primary"
className={cn(
"inline-flex items-center justify-center rounded-md px-3 py-1.5 text-sm font-medium",
"bg-emerald-500 text-white hover:bg-emerald-400",
"disabled:opacity-50 disabled:cursor-not-allowed",
)}
>
Save
</button>No theme objects, no runtime style computation—just boring, fast UI development.
Principle 5: Server-first, client only when needed
With Next.js:
- Make server components your default.
- Only reach for client components when you actually need interactivity.
- Keep client islands small and focused.
Example pattern:
page.tsx(server): fetch data, layout, pass props.FiltersClient.tsx(client): search input, dropdowns, URL syncing.Table.tsx(server or client): render hydrated data, no extra magic.
Next.js components stay understandable because each has a clear job.
Principle 6: Delete-friendly design
Design everything so that:
If you can’t delete it in under 5 minutes, it’s probably over-abstracted.
Symptoms you’ve gone too far:
- Removing a “shared” component triggers changes in unrelated features.
- Screens depend on global UI singletons.
- You’re scared to run
ripgrepbecause you know it will show usages everywhere.
Healthy UI architecture feels easy to delete, not untouchable.
These principles are the foundation of Ruixen UI.
Actionable frameworks you can use this week
Principles are nice. You still have work on Monday.
Here are concrete frameworks you can drop into your team.
Framework 1: The “one-day screen” constraint
For most product work, a non-trivial screen should fit into ~1 day of UI engineering.
Define:
- Morning: align on UX, implement the layout and states.
- Afternoon: refine styles, wiring, QA, and edge cases.
If it doesn’t fit:
- You’re over-abstracting the UI.
- You’re mixing concerns (business logic and layout in same mega-component).
- You’re trying to build a “mini design system” mid-feature.
Use the constraint to force simpler React UI instead of more clever architecture.
Framework 2: The extraction checklist
Before extracting a component:
- Do we have 3+ real call sites that need it?
- Do they share the same semantics, not just similar visuals?
- Will this abstraction reduce future complexity, or just hide it?
- Can I explain its API in under 30 seconds to another engineer?
- Can it be deleted or replaced without massive fallout?
If the answer to any of these is “no”, keep the code local.
Framework 3: Layout shells first, details later
Instead of perfecting each widget, get the layout shell right:
- Build a
Pageshell (header, content, actions). - Establish a handful of common sections:
PageHeader,PageBody,PageFooter. - Use those across screens before abstracting anything else.
Your Next.js components become:
<Page>
<PageHeader
title="Billing"
description="Manage your subscription and invoices."
actions={<UpgradeButton />}
/>
<PageBody>
<BillingSummary />
<InvoicesTable />
</PageBody>
</Page>You get consistent structure without locking yourself into an over-generalized “PageManagerLayoutEngine”.
Framework 4: The “no hero components” rule
Ban “hero” components that try to do everything:
SmartFormthat handles every field and validation strategy.DataGridthat handles every interaction ever dreamed of.LayoutManagerthat renders arbitrary page config JSON.
Instead:
- Build narrow, boring components that do one thing well.
- Compose them into more complex UIs close to the feature, not in a “core” directory.
Ruixen UI follows this exact simplicity-over-complexity philosophy.
How I applied this on real projects
On a real product, we had:
- A heavy “design system” with hundreds of components.
- A pile of nested layouts and shared wrappers.
- A checkout flow that took weeks to change.
We reset with the 1/10th-time principles:
- Deleted entire folders of unused abstractions.
- Created a small set of layout primitives (
Page,Section,Stack). - Replaced custom theming with straight Tailwind UI classes.
- Split big, client-heavy components into server-first Next.js components plus tiny client islands.
The effect:
- A new, non-trivial screen went from 3–4 days to less than a day.
- Refactors stopped feeling like surgery.
- The UI performance got better because we weren’t hauling around unnecessary wrappers and providers.
I ended up applying the same approach across multiple apps.
At some point, it made more sense to codify those patterns than to keep rewriting them.
That’s where Ruixen UI comes in.
These principles are the foundation of Ruixen UI, not an afterthought layered on top.
Ruixen UI: these principles, turned into components
Ruixen UI isn’t about clever design tokens or 200 pre-styled widgets.
It’s about:
- Small, composable building blocks.
- Tailwind-first styling, no fragile theme engines.
- Server-friendly patterns that respect how modern Next.js apps work.
- Components that are easy to read, easy to copy, and easy to delete.
Ruixen UI follows this exact simplicity-over-complexity philosophy.
If you inspect the components, you’ll see the same ideas you just read:
- Concrete first, abstraction later.
- Composition over configuration.
- Delete-friendly, not “enterprise” clever.
If you’re tired of fighting your own UI
If you’re honest with yourself, you probably know whether your UI is over-engineered.
Ask:
- Does a new screen feel like plugging pieces together, or negotiating with old decisions?
- Are you building product, or maintaining a fragile internal framework?
- Can a new engineer ship a meaningful change in their first week?
If not, start applying the 1/10th-time principles:
- Build one screen plainly.
- Delay abstractions.
- Let Tailwind and simple Next.js components do the heavy lifting.
- Make deletion safe, not scary.
And if you’re curious what a UI layer built from day one with these constraints looks like in practice, go skim Ruixen UI—look at the source, the patterns, and how little machinery is actually required to build fast, reliable interfaces.


