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

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

References