Skip to main content

On This Page

Draft / Scheduled Content

This article is a draft or scheduled for future publication. The content is subject to change.

The CSS-in-JS Fever Dream is Over

5 min read
Share

The Compulsion to Co-locate

In the mid-2010s, React popularized a new paradigm: “Component-Driven Development.” We combined HTML structure and JavaScript logic into a single file called JSX.

It was a revolution. Having HTML and JS in one place made UI code much easier to reason about.

Naturally, frontend developers looked at CSS and asked: “Why is styling still sitting in separate .css files? Why can’t we co-locate our styles inside our JavaScript components too?”

Thus, CSS-in-JS was born. Libraries like Styled Components, Emotion, and JSS dominated the React ecosystem. We wrote CSS inside JavaScript template literals, dynamically passing props to change colors and layouts at runtime:

// The CSS-in-JS era
const Button = styled.button`
  background: ${props => props.primary ? 'blue' : 'gray'};
  color: white;
  padding: 12px;
  border-radius: 4px;
`;

It felt modern. It felt clean. It felt like we had finally solved CSS scoping.

It was also a disaster for web performance.

Ten years later, the industry has realized that parsing, compiling, and injecting CSS using JavaScript at runtime is an architectural dead-end. The CSS-in-JS fever dream is finally over.

The Performance Cost of Runtime Style Injection

When you write styles in a standard .css file, the browser downloads the CSS, parses it, and applies it to the DOM. This happens in parallel with HTML parsing, and the browser’s CSS rendering engine is highly optimized at the C++ level.

When you use a runtime CSS-in-JS library, the browser has to do a ridiculous amount of work:

  1. JS Bundle Bloat: You must ship the CSS-in-JS library’s parser and runtime code (often 15KB-30KB gzipped) to the user’s browser.
  2. CPU Serialization Overhead: Every time a component renders or its props change, the library’s JS runtime must parse your template literals, generate a unique class name hash, serialize the CSS string, and check if it has already injected those styles.
  3. DOM Thrashing: The library must dynamically insert a new <style> tag into the HTML document head. This causes the browser to recalculate styles for the entire page, triggering expensive layout repaints (reflows).

This runtime tax is especially high for complex interfaces with hundreds of elements. If you scroll a list where items dynamically calculate styles via props, your frame rate drops, and the scrolling feels laggy.

We traded the browser’s fast, native CSS compiler for a slow, JavaScript-based simulator.

Runtime CSS-in-JS Flow (Slow)
+-------------------------------------------------------------+
|  React Render -> JS evaluates props -> Serializes CSS ->    |
|  Generates Hash -> Injects <style> tag -> Browser Recalc    |
+-------------------------------------------------------------+

Utility/Native CSS Flow (Fast)
+-------------------------------------------------------------+
|  React Render -> Appends static class name -> Browser      |
|  instantly applies pre-parsed CSS rule                      |
+-------------------------------------------------------------+

The SSR/Hydration Nightmare

As the industry moved toward Server-Side Rendering (SSR) with frameworks like Next.js, CSS-in-JS went from performing poorly to being outright broken.

To render a page on the server with Styled Components, you have to intercept the render tree, extract the styles generated during the render, and manually inject them into the HTML head so the page doesn’t flash unstyled content (FOUC) when it loads.

With the introduction of React Server Components (RSC) in React 18, runtime CSS-in-JS hit a brick wall. Server Components do not support runtime JavaScript context on the server. Because Styled Components relies on React Context to pass themes, it cannot be used inside Server Components without turning them into Client Components, defeating the entire performance benefit of RSC.

The creators of Emotion and Styled Components have acknowledged this limitation. The architecture simply cannot support the future of the web.

The Winners: Tailwind and Zero-Runtime CSS

So, what won? Two main approaches have taken over the frontend world:

1. Tailwind CSS (Utility-First)

Tailwind bypassed the runtime issue by compiling styles at build time.

Instead of writing CSS inside JS, you write utility classes directly in your HTML:

<button class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded">
  Click me
</button>

At build time, Tailwind scans your code and generates a single, minimal CSS file containing only the classes you actually used.

  • Zero runtime JS overhead: The browser loads a static, highly cacheable CSS file.
  • Instant rendering: No <style> tag injection at runtime.
  • Component co-location: You still see the styles right next to the markup.

Tailwind won because it solved the developer experience of co-location without charging the user a performance tax.

2. Modern Native CSS

While frontend developers were playing with JS runtimes, native CSS got incredibly good.

Features that previously required Sass or JavaScript are now supported natively by every major browser:

  • CSS Custom Properties (Variables): Allow dynamic styling without JS serialization. You can update a variable in JS, and the browser recalculates the styles natively:
    .button {
      background-color: var(--btn-bg, gray);
    }
  • CSS Nesting: Write structured CSS directly in standard files without a preprocessor.
  • Container Queries: Style elements based on the size of their parent container rather than the screen viewport, making truly modular components possible.

Write CSS, Not JavaScript

The CSS-in-JS era was born out of a fear of CSS. Developers didn’t want to learn the cascade, scoping rules, or layout mechanics, so they tried to turn CSS into JavaScript.

In doing so, they ignored the platform. The web platform is built on HTML, CSS, and JS working in harmony. Trying to run all three through the JS thread is a recipe for slow, heavy sites.

Ditch the runtime styles. Write static CSS, use Tailwind, or leverage native custom properties. Your users’ CPU cycles are too precious to waste on parsing styling strings.

Related Content