CSS-in-JS
CSS-in-JS is an approach where component styles are written in JavaScript or TypeScript files instead of separate stylesheets. Libraries generate scoped class names automatically, so styles can't leak between components, and they let styles depend on props, themes, and application state. The approach became popular with React around 2016–2019, led by styled-components and Emotion.
The landscape has since split. Runtime libraries inject styles in the browser while the app runs, which is flexible but costs performance and fits poorly with React Server Components and streaming SSR. Zero-runtime (compile-time) libraries — vanilla-extract, Panda CSS, StyleX, Linaria — keep the authoring experience but extract static CSS files at build time. Many teams have also moved to Tailwind or CSS Modules.
TL;DR
- CSS-in-JS co-locates scoped styles with components, written in JS/TS.
- Runtime libraries (styled-components, Emotion) compute styles in the browser — flexible, but add JS and render cost.
- Zero-runtime libraries (vanilla-extract, Panda CSS, StyleX, Linaria) compile to static CSS at build time.
- Implement dynamic styles with CSS variables rather than regenerating classes per prop value.
- Runtime CSS-in-JS needs extra work for SSR and generally doesn't work in Server Components.
- For new projects, prefer zero-runtime options, CSS Modules, or Tailwind.
Quick Example
A button with variants in a runtime library (styled-components):
The same component with a zero-runtime library (vanilla-extract), compiled to a static .css file:
Core Concepts
How Runtime CSS-in-JS Works
When a styled component renders, the library serializes the style template with current props, hashes it into a class name, and inserts a <style> rule into the document if it doesn't exist yet. This happens during rendering in the browser (and on the server for SSR), adding JavaScript bundle size and CPU work, especially when styles change frequently.
Zero-Runtime Extraction
Compile-time libraries evaluate style definitions during the build and emit plain CSS files with generated class names. At runtime, components just apply class names. You keep type safety, co-location, and theming, and lose only styles that depend on arbitrary runtime values — which CSS variables handle.
Library Landscape
Theming and Tokens
Themes provide colors, spacing, typography, and radii through context (runtime libraries) or CSS custom properties (zero-runtime). Exposing tokens as CSS variables enables dark mode and brand switching without re-rendering. See Design Tokens.
Dynamic Styles
Instead of generating a new class for every prop value (width: ${progress}%), set a CSS variable inline and reference it in a static class:
SSR and Server Components
Runtime libraries must collect styles during server rendering and inject them into the HTML to avoid a flash of unstyled content, which requires framework-specific setup (a style registry in Next.js). They depend on React context, so they only work in Client Components, not React Server Components. Zero-runtime libraries have no such limitation. See React Server Components.
Best Practices
Prefer Zero-Runtime for New Projects
You get co-location and types without browser runtime cost or RSC incompatibility.
Use CSS Variables for Values That Change Often
Themes, animations, and user-controlled sizes shouldn't regenerate classes on every render.
Keep a Token-Based Theme
Centralize design decisions and avoid hard-coded colors and spacing scattered across components.
Define Styles Outside Render Functions
Creating styled components or style objects inside a component body recreates them each render and can break React reconciliation.
Measure Before Migrating
If an existing runtime CSS-in-JS app performs well enough, migrate incrementally, starting with the most rendered components.
Common Mistakes
Styled Components Declared Inside Components
This creates a new component type every render, remounting children and losing state.
Interpolating Rapidly Changing Values
Passing scroll position or animation frames as props generates new classes constantly and floods the style sheet.
Missing SSR Setup
Without server style collection, pages flash unstyled content on first load.
Mixing Several Styling Systems
Tailwind plus Emotion plus Sass in one codebase makes specificity and ownership confusing. Standardize.
Ignoring Specificity Conflicts With Libraries
Overriding component-library styles can depend on insertion order. Use the library's supported override APIs or cascade layers.
Comparison
FAQ
What is CSS-in-JS?
An approach to styling where CSS is authored in JavaScript or TypeScript alongside components, with libraries generating scoped class names and optionally computing styles from props or themes.
Is CSS-in-JS bad for performance?
Runtime CSS-in-JS adds JavaScript and style-injection work during rendering, which can hurt performance in large or frequently updating apps. Zero-runtime libraries avoid this by extracting static CSS at build time.
Can I use styled-components with React Server Components?
Only inside Client Components, since runtime libraries rely on React context and browser-side injection. Zero-runtime libraries work in both.
What should I use instead of runtime CSS-in-JS?
vanilla-extract, Panda CSS, or StyleX for a similar authoring experience with zero runtime; CSS Modules for plain CSS with scoping; or Tailwind for utility-first styling.
Related Topics
- Tailwind CSS — A popular utility-first alternative
- Sass / SCSS — Preprocessed stylesheets
- React — Where CSS-in-JS is most common
- Design Tokens — Theming foundations
- Web Performance — Runtime cost and render performance