The CSS-in-JS landscape has shifted significantly. The 2020 era of “styled-components is the default” is over. Modern frontend leans toward Tailwind, CSS Modules, or zero-runtime CSS-in-JS like vanilla-extract. Frontend interviews increasingly probe whether you understand the runtime tradeoffs.
The runtime cost problem
Traditional CSS-in-JS (styled-components, Emotion) runs at runtime:
- Component render generates CSS string
- String inserted into
<style>tag - Hash computed for class name
This adds JavaScript work on every render. For complex apps, performance impact is meaningful.
The 2026 options
Tailwind CSS
Utility-first CSS via class names. Build-time compiled. Zero runtime cost. Atomic CSS via PurgeCSS.
Pros: tiny bundle, fast, no naming bikeshed, IDE autocomplete
Cons: HTML can look noisy, learning curve, CSS expert can produce more elegant solutions
CSS Modules
Plain CSS files scoped to components by build tooling. Class names auto-namespaced.
Pros: standard CSS, scoped, zero runtime, well-supported
Cons: no dynamic styling without escape hatches, more verbose than utilities
vanilla-extract
TypeScript-typed CSS at build time. Zero runtime, type-safe.
Pros: best of CSS-in-JS DX with no runtime cost
Cons: smaller community, learning curve
Tailwind + arbitrary CSS for edge cases
Most pragmatic 2026 stack: Tailwind for 90% of styles, CSS Modules or scoped styles for the 10% that does not fit utilities.
styled-components / Emotion
Still in use in legacy codebases. Runtime cost is real. New projects rarely pick.
The Tailwind debate
Tailwind is divisive. Arguments:
Pro:
- Fast — ship a feature without writing CSS
- Consistent — design tokens enforced
- Composable — utility classes mix predictably
- Zero runtime cost
Con:
- HTML noise:
class="flex items-center justify-between p-4 rounded-lg bg-blue-500" - Less readable for designers
- Customization requires extending config
Most modern startups (Vercel, Linear, Cal.com, Resend) use Tailwind in 2026.
Component abstraction
Tailwind’s noise problem is mitigated by component extraction:
function Button({ variant, children }) {
return (
<button className={cn(
"px-4 py-2 rounded font-medium",
variant === "primary" && "bg-blue-600 text-white",
variant === "secondary" && "bg-gray-200 text-gray-900"
)}>{children}</button>
);
}
Tailwind in components, components in pages.
Design system integration
Tailwind config defines:
- Colors (semantic tokens)
- Spacing scale
- Typography scale
- Breakpoints
- Custom utilities
The config IS the design system’s tokens. Treat it as a library.
Migration patterns
Migrating from styled-components to Tailwind:
- Component-by-component
- Use both during migration (no global conflict)
- Establish design tokens first, then port components
- Codemods help; manual review catches edge cases
Server Components and CSS-in-JS
Most CSS-in-JS libraries (styled-components, Emotion) do not work in React Server Components — they need a client runtime.
Tailwind, CSS Modules, and vanilla-extract work fine in RSC. This is one of the major drivers of the migration.
Common mistakes
- Pure inline styles (no theme tokens, no responsive)
- !important everywhere (cascade is broken)
- CSS-in-JS at runtime in performance-critical apps
- No design tokens — every team picks their own colors
Frequently Asked Questions
Should I migrate from styled-components?
If your app is performance-critical or moves to Server Components, yes. Otherwise, no urgent need; migrate over time.
Is Tailwind here to stay?
Mainstream and growing. Hard to imagine a sudden reversal. The next shift may be toward more native CSS features (cascade layers, container queries) used directly.
Can I mix Tailwind and CSS Modules?
Yes. Tailwind for the common case; CSS Modules for complex or animation-heavy components. Common pragmatic stack.