Summary
CSS Modules, runtime CSS-in-JS (styled-components/Emotion), zero-runtime CSS-in-JS (vanilla-extract, StyleX, Linaria), and utility-first (Tailwind) sit on a spectrum from static to dynamic. Runtime CSS-in-JS injects styles from JavaScript — fast to author but incompatible with React Server Components. Zero-runtime and Modules compile to static files, keeping the server boundary clean.
Jump to the interview angleCSS strategy spectrum
A range of styling approaches that differ in when styles are generated: at build time (CSS Modules, zero-runtime CSS-in-JS, utility-first) or at runtime in the browser (runtime CSS-in-JS). Build-time approaches produce static CSS files; runtime approaches produce styles by executing JavaScript on the client, which blocks RSC adoption because Server Components run on the server with no browser JS context.
Approach × runtime cost / RSC-compat / DX
| Approach | Runtime JS cost | RSC compatible | DX | |
|---|---|---|---|---|
| CSS Modules | None — static .css files | Yes — plain static import | Scoped classes; no colocated variants | |
| Runtime CSS-in-JS (styled-components, Emotion) | High — style insertion runs per render | No — requires client JS context | Props-driven variants; full TS colocation | |
| Zero-runtime CSS-in-JS (vanilla-extract, StyleX, Linaria) | None — compiled to static CSS at build | Yes — outputs .css files | Type-safe; colocated; no dynamic props at runtime | |
| Utility-first (Tailwind CSS) | None — static utility classes; JIT purges unused | Yes — class strings only | Fast iteration; verbose JSX; design-token constraints |
Why runtime CSS-in-JS breaks RSC
styled-components and Emotion insert styles by calling document.createElement('style') at render time. React Server Components execute on the server (or at build time) with no DOM — that call throws. Wrapping every styled component in 'use client' is possible but defeats RSC's bundle-splitting goal. Teams migrating to the App Router switched to vanilla-extract or Tailwind.
How to choose
Greenfield RSC app: Tailwind (fast, zero overhead) or vanilla-extract (type-safe, colocated). Legacy SPA staying on client-only React: runtime CSS-in-JS is fine. Design-system package shared across RSC and non-RSC consumers: zero-runtime (vanilla-extract or StyleX). Thin component library with no styling opinions: CSS Modules.
Interview angle
Interviewers ask why teams moved off styled-components when adopting the Next.js App Router, or how you would style a shared design-system that ships to RSC consumers. Name the DOM-injection mechanism and the RSC constraint specifically.
Soundbite: "Runtime CSS-in-JS writes styles via JavaScript — that's incompatible with a server that has no DOM."
Key terms
- Runtime CSS-in-JS
- Styles generated and injected into the DOM by JavaScript executing in the browser.
- Zero-runtime CSS-in-JS
- CSS-in-JS tooling (vanilla-extract, StyleX, Linaria) that compiles styles to static files at build time.
- CSS Modules
- Locally-scoped CSS via build-time class-name hashing; no JS at runtime.
- Utility-first CSS
- Tailwind's approach: a fixed set of single-purpose classes; unused ones are purged at build.
- JIT (Tailwind)
- Just-in-time compiler that scans source files and emits only the utility classes actually used.