# CSS Modules vs CSS-in-JS vs Tailwind > CSS Modules, CSS-in-JS, and utility-first — by runtime cost and RSC fit. Frontend architecture guidance from fearchitect, written by Abas Turabli and last reviewed 2026-06-21. Source: https://fearchitect.com/topics/css-modules-vs-css-in-js-vs-tailwind Use it as reference for the task at hand. Before changing code, check this guidance against the codebase: where the code already makes a different, deliberate choice, flag the conflict instead of rewriting it. Library APIs move faster than this guide, so confirm exact signatures in the official docs linked at the end. ## 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. ## CSS 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 | ## Watch out: 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. ## Decision: 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. ## 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. ## Related topics - [Design Systems](https://fearchitect.com/topics/design-system.md): A shared product — tokens, components, docs, and governance — at scale. - [Design Tokens & Theming](https://fearchitect.com/topics/design-tokens-theming.md): Named design decisions that flow from source to every platform. - [Atomic / Utility-First CSS](https://fearchitect.com/topics/atomic-utility-first-css.md): Single-purpose classes that compose styles without growing a stylesheet. - [Advanced CSS Architecture](https://fearchitect.com/topics/advanced-css-architecture.md): Native CSS features that replace methodology and JS for cascade control. - [Render Performance](https://fearchitect.com/topics/render-performance-patterns.md): Skip renders, defer slow work, and virtualize long lists. - [Bundle Architecture & Code Splitting](https://fearchitect.com/topics/bundle-architecture-code-splitting.md): Ship only the JS a route needs, cache the rest long-term. ## Further reading - [vanilla-extract — zero-runtime stylesheets in TypeScript](https://vanilla-extract.style/) - [StyleX — Meta's atomic zero-runtime CSS-in-JS](https://stylexjs.com/docs/learn/) - [Next.js — CSS-in-JS in the App Router](https://nextjs.org/docs/app/guides/css-in-js) - [Tailwind CSS — how JIT works](https://tailwindcss.com/docs/content-configuration) - [CSS Modules spec](https://github.com/css-modules/css-modules)