All posts
css-in-jscss

CSS-in-JS: A Practical Guide for Full-Stack Developers

A practical guide to CSS-in-JS — colocating styles with components, runtime vs build-time approaches, and where the pattern still fits in 2026.

SR

Suhail Roushan

August 6, 2026

·
5 min read
·
0 views

CSS-in-JS peaked in popularity when component-scoped styling and dynamic theming were hard problems to solve any other way — since then, CSS Modules, native CSS nesting, and utility-first frameworks like Tailwind have each chipped away at the specific pain points CSS-in-JS was built to solve, which is worth understanding before reaching for it by default.

CSS-in-JS is a pattern where component styles are written in JavaScript/TypeScript, colocated with the component itself, rather than in separate .css files. Libraries implementing this pattern (styled-components, Emotion, vanilla-extract) vary significantly in whether styles are computed at runtime (in the browser) or extracted at build time — a distinction with real performance implications.

Why CSS-in-JS Matters (and When to Skip It)

Colocating styles with components and having full access to JavaScript logic for dynamic styling (props-based conditional styles, computed values) is genuinely convenient for component libraries with significant prop-driven visual variation — the styling logic lives next to the component logic it's tied to, rather than in a separate stylesheet you have to cross-reference mentally.

Skip runtime CSS-in-JS specifically for performance-sensitive applications, since runtime style computation and injection has a real cost (JavaScript execution generating styles, injecting them into the DOM, and this cost happens on every render in some implementations) that build-time alternatives (CSS Modules, vanilla-extract, Tailwind) avoid entirely by resolving styles ahead of time.

Getting Started with CSS-in-JS

A styled-components example, showing the colocated, dynamic-styling pattern:

import styled from "styled-components";

const Button = styled.button<{ variant: "primary" | "secondary" }>`
  padding: 0.5rem 1rem;
  background-color: ${(props) => (props.variant === "primary" ? "#3b82f6" : "gray")};
  color: white;

  &:hover {
    opacity: 0.9;
  }
`;

function App() {
  return <Button variant="primary">Click me</Button>;
}

A build-time alternative (vanilla-extract), avoiding runtime style computation:

// button.css.ts
import { style } from "@vanilla-extract/css";

export const button = style({
  padding: "0.5rem 1rem",
  backgroundColor: "#3b82f6",
  ":hover": { opacity: 0.9 },
});

Core CSS-in-JS Concepts Every Developer Should Know

Runtime CSS-in-JS computes and injects styles in the browser, which means a genuine performance cost (particularly with server-side rendering, where styles need to be extracted and injected during SSR to avoid a flash of unstyled content) — this runtime cost is the main reason the CSS-in-JS category as a whole has faced more scrutiny in recent years as performance-conscious defaults have shifted.

Build-time (zero-runtime) CSS-in-JS libraries generate static CSS files at build time, keeping the colocation and type-safety developer experience benefits of CSS-in-JS while avoiding the runtime cost — vanilla-extract and similar tools represent this middle ground, and are generally the better choice if you specifically want CSS-in-JS's DX without its historical performance downsides.

React Server Components complicate runtime CSS-in-JS significantly, since many runtime CSS-in-JS libraries rely on React context and client-side JavaScript execution patterns that don't map cleanly onto the server-component model — this is a meaningful, concrete reason several teams have moved away from runtime CSS-in-JS specifically in Next.js App Router projects.

Dynamic, prop-driven styles are CSS-in-JS's clearest remaining advantage over CSS Modules or Tailwind — when a component's styling genuinely depends on complex runtime logic beyond what CSS custom properties comfortably express, CSS-in-JS's direct JavaScript access is a real ergonomic benefit, though this is a narrower use case than CSS-in-JS's original broad popularity suggested.

Common CSS-in-JS Mistakes and How to Fix Them

Mistake 1: reaching for runtime CSS-in-JS by default without considering its performance cost or React Server Components compatibility, particularly in a Next.js App Router project. Fix: evaluate build-time alternatives (vanilla-extract, CSS Modules, Tailwind) first, and use runtime CSS-in-JS only where its specific dynamic-styling benefit is genuinely needed.

Mistake 2: not properly configuring SSR style extraction, causing a flash of unstyled content on initial page load. Fix: follow your CSS-in-JS library's specific SSR integration guide carefully, since misconfiguration here is a common and visible bug.

Mistake 3: using CSS-in-JS for styles that don't actually need dynamic, prop-driven logic, when a static CSS Module or utility classes would be simpler and avoid the runtime cost entirely. Fix: reserve CSS-in-JS's dynamic capabilities for cases that actually need them, using simpler static styling for everything else.

When Should You Use CSS-in-JS Instead of CSS Modules or Tailwind?

Use CSS-in-JS (preferably a build-time/zero-runtime option) when component styling genuinely depends on complex, prop-driven logic that's awkward to express with CSS custom properties or conditional class names. Use CSS Modules or Tailwind for most other styling needs, where their build-time resolution and simpler mental model avoid CSS-in-JS's historical performance and React Server Components compatibility concerns.

CSS-in-JS in Production

If already using runtime CSS-in-JS in a Next.js App Router project, verify actual React Server Components compatibility carefully, since this is an active pain point for several popular runtime libraries. Consider migrating toward a build-time/zero-runtime alternative (vanilla-extract) for new work if colocation and type safety matter but the runtime cost doesn't.

If you're starting a new project and reaching for CSS-in-JS out of habit, evaluate Tailwind or a build-time option first — the specific problems runtime CSS-in-JS was built to solve are now handled well by tools without its performance and SSR complexity tradeoffs.

Related posts

Written by Suhail Roushan — Full-stack developer. More posts on AI, Next.js, and building products at suhailroushan.com/blog.

Get in touch