Key Takeaways
- 01 Plain CSS is 24-41% smaller than CSS-in-JS solutions (no runtime overhead)
- 02 Modern CSS features (nesting, custom properties, cascade layers) solve old problems
- 03 Debugging is faster with plain CSS - right in browser dev tools
- 04 CSS-in-JS still makes sense for component libraries and complex dynamic styling
- 05 Bundle size, performance, and simplicity favor plain CSS for most apps
The CSS-in-JS Dream Is Over (And That’s Actually Good)
Three years ago, I was preaching the gospel of CSS-in-JS. “Type-safe styling! Component-scoped styles! No more cascade nightmares!” I converted my entire React codebase to styled-components, then later to Tailwind.
Last month? I ripped it all out. And I’m not the only one.
What happened isn’t a bug or framework limitation — it’s a collective realization that we might have overengineered a solution to a problem that wasn’t really there.
What happened isn’t a bug or framework limitation — it’s a collective realization that we might have overengineered a solution to a problem that wasn’t really there.
What We Thought We Were Solving
In 2019-2021, the pitch for CSS-in-JS was compelling:
Type Safety
“Your styles won’t break at runtime because TypeScript catches it”
I ran into this exact problem twice in five years. Both were copy-paste errors where I used the wrong className. Both were fixed in five minutes.
Component Scoping
“No more global namespace conflicts”
Global CSS conflicts were a problem in 2015. In 2026? With CSS custom properties, modules, and modern build tools? Almost never happens unless you’re doing something wrong.
Co-Location
“Styles live with their components”
This is nice. It is convenient. But it turns out, the benefits are smaller than the costs we’ve been ignoring.
Component scoping is nice and convenient, but the benefits are smaller than the costs we’ve been ignoring.
The Hidden Costs We Ignored
1. The Bundle Size Tax
Real numbers from a production React app I audited last month:
| Approach | JS Bundle (gzipped) | CSS Bundle (gzipped) | Total |
|---|---|---|---|
| Tailwind CSS | 89KB | 8KB | 97KB |
| Styled Components | 72KB | 3KB | 75KB |
| Plain CSS + CSS Modules | 45KB | 12KB | 57KB |
Plain CSS wins. By 24-41% on JS bundle size alone.
Why? Because CSS-in-JS libraries ship a runtime. Styled-components is 17KB gzipped. Emotion is 14KB. That’s runtime that does work browsers have done natively for 20 years.
Why? Because CSS-in-JS libraries ship a runtime. Styled-components is 17KB gzipped. Emotion is 14KB. That’s runtime that does work browsers have done natively for 20 years.
2. The Debugging Nightmare
Remember when you could inspect an element, see the CSS, and just… change it?
Now with styled-components:
- Inspect element
- See
class="StyledComponent-sc-1b2c3d" - Search codebase for that generated class
- Find component
- Scroll to styled definition
- Wait, it’s using props, so I need to…
- Realize styled component is in a different file
- Repeat steps
With Tailwind, it’s better but still involves searching for utility classes. With plain CSS? Right there in the browser dev tools, one click to edit.
With plain CSS? Right there in browser dev tools, one click to edit. No searching codebase for generated classes.
3. The Documentation Gap
Tailwind’s documentation is excellent. But it’s 5,000 lines because you need to know every utility.
Plain CSS? The MDN documentation covers everything you need — and it’s written for CSS, not for a framework-specific abstraction.
What Changed in 2026 That Made Plain CSS Viable Again
CSS Custom Properties (Variables)
We finally have proper variables:
:root {
--color-primary: #6366f1;
--spacing-md: 1.5rem;
}
.card {
background: var(--color-primary);
padding: var(--spacing-md);
}
Theming? Easy. Dark mode? One media query and update variables. This used to require complex CSS-in-JS theming providers.
CSS Nesting
Finally here in all major browsers:
.card {
background: var(--bg-secondary);
border: 1px solid var(--border-subtle);
&:hover {
border-color: var(--accent);
}
.title {
font-size: 1.5rem;
}
}
This addresses the original “component scoping” problem without any runtime overhead.
Cascade Layers
@layer base, components, utilities;
@layer components {
.card { /* Component styles */ }
}
Specificity control without !important or complex selectors. Components can’t override base styles unexpectedly.
Custom properties, nesting, and cascade layers solve the original problems that drove us to CSS-in-JS — without any runtime overhead.
My Experience Going Back
I migrated our dashboard from Tailwind to plain CSS modules. Here’s what happened:
Initial Speed: Slower at first. I was reaching for text-lg and flex-col that don’t exist.
Week 1: Annoying. “How do I center this again?” (It’s display: grid; place-items: center; — two properties, so much clearer).
Week 2: Click. The mental model came back. I remembered fundamentals. I wasn’t fighting an abstraction anymore.
Month 1: Faster. I write what I mean. The CSS is readable, not a 50-class string that requires decoding.
Production: Smaller bundles, faster load times, easier debugging.
Smaller bundles, faster load times, easier debugging. Week 2 was when it clicked — the mental model came back.
When CSS-in-JS Actually Makes Sense
I’m not saying CSS-in-JS is bad. It’s just overused. Use it when:
✅ You’re building a component library that needs to be framework-agnostic ✅ You need dynamic styling based on complex props that CSS variables can’t handle ✅ You’re doing code-splitting where CSS needs to be bundled with specific chunks ✅ You’re building a design system with multiple teams and need strict component isolation
When Plain CSS Is Better
Use plain CSS (or CSS modules) when:
✅ Building a standalone app or site ✅ Performance matters (it always should) ✅ You want faster debugging (you always should) ✅ You value bundle size (you always should) ✅ You’re a small to medium team
Plain CSS wins for standalone apps, performance, debugging, bundle size, and small teams. CSS-in-JS for component libraries and complex dynamic styling.
The Future I’m Betting On
I think 2026-2027 will see a swing back toward plain CSS.
The frontend community is tired of tooling fatigue. We want simpler, faster, more understandable. The new CSS features (nesting, layers, container queries, cascade scope) finally give us the primitives we need without a runtime overhead.
CSS-in-JS isn’t dying. It’s just becoming niche again — which is exactly where it should have stayed.
CSS-in-JS isn’t dying. It’s just becoming niche again — which is exactly where it should have stayed.
What You Should Do Today
If you’re on Tailwind or a CSS-in-JS library:
- Don’t rip it out mid-project — that’s a recipe for pain
- Try plain CSS for your next component — start small
- Learn modern CSS features — they’re powerful now
- Measure your bundle size — the numbers will surprise you
- Decide based on your actual needs, not what’s trendy
My Take
After three years in the CSS-in-JS world, I’m back to plain CSS. My code is smaller, my debugging is faster, and my mental model is simpler.
Sometimes the trend is right. Sometimes it’s noise. Smart developers know the difference. And in 2026? Plain CSS is the smart choice.
Sometimes the trend is right. Sometimes it’s noise. Smart developers know the difference.
And in 2026? Plain CSS is the smart choice.