Css In Js vs Legacy Css
CSS-in-JS promised co-located, scoped styles and delivered runtime cost and server-component friction. Plain CSS grew up — scoping, nesting, variables, layers — and quietly won. Pick boring CSS.
The short answer
Legacy Css over Css In Js for most cases. The platform caught up and the runtime tax never went away.
- Pick Css In Js if shipping a client-rendered SPA, want styles co-located with components, and accept the runtime cost — or you commit to a zero-runtime variant like Linaria, vanilla-extract, or Panda that compiles to static CSS at build time
- Pick Legacy Css if want the default: maximum performance, RSC compatibility, no hydration overhead, and a styling layer that outlives whatever framework you're using this year. This is almost everyone
- Also consider: Tailwind or CSS Modules if you want component-scoped ergonomics without either a runtime or hand-managing global class collisions. CSS Modules in particular is 'legacy CSS' with the one missing feature bolted on cleanly.
— Nice Pick, opinionated tool recommendations
The pitch CSS-in-JS made
CSS-in-JS arrived to fix real pain: global namespaces where one stray selector nukes an unrelated component, dead styles nobody dares delete, and the mental tax of jumping between a .css file and a .jsx file. styled-components and Emotion co-located styles with markup, scoped them automatically via generated class names, and let you interpolate props straight into rules — dynamic theming without a class-name switchboard. For 2017-era React, this was genuinely better than the BEM-and-prayer status quo. The DX was the selling point and it delivered: autocomplete, dead-code elimination, and styles that traveled with the component you copied. The problem is that none of this was free, and the bill came due exactly where React itself moved next: the server.
Where the runtime tax bites
Runtime CSS-in-JS does work in the browser that plain CSS does at build time: it parses your style objects, serializes them to CSS, injects <style> tags, and manages a cache — on mount, and often on re-render. Emotion and styled-components add bundle weight plus per-render CPU you can measure in flame charts on large trees. Worse, they fight React Server Components: hooks and context that the runtime depends on don't exist server-side, so you're wrapping everything in client boundaries or fighting hydration mismatches. The styled-components team itself put the library into maintenance mode and pointed users elsewhere. When the flagship project tells you to stop, that's not FUD — that's the maintainers conceding the architecture lost to the platform.
Legacy CSS grew teeth
The 'legacy' label is a lie now. Native nesting shipped across browsers, so your selectors read like the component tree. @scope gives real scoping without a build step. Custom properties do runtime theming that CSS-in-JS interpolation always envied — you flip a variable and the whole tree re-themes with no JS re-render. Cascade layers (@layer) finally make specificity predictable, killing the !important wars. CSS Modules hand you guaranteed-unique class names at build time for the one thing the platform doesn't fully solve. Add container queries and you have responsive components without measuring in JS. This is the same feature list CSS-in-JS sold — delivered by the engine that's already running, at zero runtime cost. You don't ship a library to get it; you just write CSS.
The honest exception: zero-runtime
I'm not pretending the co-location DX was worthless — I'm saying you can have it without the runtime. vanilla-extract, Linaria, and Panda CSS let you write styles in TypeScript, get type-safe tokens and autocomplete, and then extract everything to static .css at build time. No browser-side serialization, no RSC breakage, no per-render cost. That's the actual successor to styled-components, and notice what it compiles to: plain stylesheets. If the cutting-edge tooling's whole job is to turn your fancy authoring format back into legacy CSS so it's fast, the legacy CSS was the destination all along. Choose zero-runtime if the typed-token ergonomics earn their build complexity; choose plain CSS plus Modules if they don't. Either way, runtime CSS-in-JS is the one box left unchecked.
Quick Comparison
| Factor | Css In Js | Legacy Css |
|---|---|---|
| Runtime cost | Browser-side serialization and style injection per render (runtime variants) | Zero runtime — styles are static and parsed by the browser's CSS engine |
| React Server Components | Breaks without client boundaries; hydration friction | Fully compatible, no client boundary required |
| Style scoping | Automatic via generated class names | Native @scope, CSS Modules, or nesting — solved without a library |
| Dynamic theming | Prop interpolation, but triggers JS re-render | CSS custom properties re-theme with no JS re-render |
| Authoring DX / co-location | Excellent: styles live with the component, typed tokens | Separate files, though CSS Modules and zero-runtime tools close the gap |
The Verdict
Use Css In Js if: You're shipping a client-rendered SPA, want styles co-located with components, and accept the runtime cost — or you commit to a zero-runtime variant like Linaria, vanilla-extract, or Panda that compiles to static CSS at build time.
Use Legacy Css if: You want the default: maximum performance, RSC compatibility, no hydration overhead, and a styling layer that outlives whatever framework you're using this year. This is almost everyone.
Consider: Tailwind or CSS Modules if you want component-scoped ergonomics without either a runtime or hand-managing global class collisions. CSS Modules in particular is 'legacy CSS' with the one missing feature bolted on cleanly.
Css In Js vs Legacy Css: FAQ
Is Css In Js or Legacy Css better?
Legacy Css is the Nice Pick. The platform caught up and the runtime tax never went away. Native CSS now ships scoping, nesting, custom properties, and cascade layers — the exact problems CSS-in-JS existed to solve — with zero runtime, zero hydration cost, and full React Server Component compatibility. Runtime CSS-in-JS (styled-components, Emotion) serializes styles in the browser on every render and breaks in RSC without contortions. The ecosystem already voted with its feet: styled-components is in maintenance mode, and the momentum is in zero-runtime tooling that compiles back to plain stylesheets. If your "modern" choice has to be compiled into the legacy thing to be fast, the legacy thing was the answer.
When should you use Css In Js?
You're shipping a client-rendered SPA, want styles co-located with components, and accept the runtime cost — or you commit to a zero-runtime variant like Linaria, vanilla-extract, or Panda that compiles to static CSS at build time.
When should you use Legacy Css?
You want the default: maximum performance, RSC compatibility, no hydration overhead, and a styling layer that outlives whatever framework you're using this year. This is almost everyone.
What's the main difference between Css In Js and Legacy Css?
CSS-in-JS promised co-located, scoped styles and delivered runtime cost and server-component friction. Plain CSS grew up — scoping, nesting, variables, layers — and quietly won. Pick boring CSS.
How do Css In Js and Legacy Css compare on runtime cost?
Css In Js: Browser-side serialization and style injection per render (runtime variants). Legacy Css: Zero runtime — styles are static and parsed by the browser's CSS engine. Legacy Css wins here.
Are there alternatives to consider beyond Css In Js and Legacy Css?
Tailwind or CSS Modules if you want component-scoped ergonomics without either a runtime or hand-managing global class collisions. CSS Modules in particular is 'legacy CSS' with the one missing feature bolted on cleanly.
The platform caught up and the runtime tax never went away. Native CSS now ships scoping, nesting, custom properties, and cascade layers — the exact problems CSS-in-JS existed to solve — with zero runtime, zero hydration cost, and full React Server Component compatibility. Runtime CSS-in-JS (styled-components, Emotion) serializes styles in the browser on every render and breaks in RSC without contortions. The ecosystem already voted with its feet: styled-components is in maintenance mode, and the momentum is in zero-runtime tooling that compiles back to plain stylesheets. If your "modern" choice has to be compiled into the legacy thing to be fast, the legacy thing was the answer.
Related Comparisons
Disagree? nice@nicepick.dev