The CSS in JavaScript Frameworks Debate

by Daniel Reeves
The CSS in JavaScript Frameworks Debate

It was a Tuesday afternoon in 2018 when a senior engineer on my team slammed his laptop shut hard enough to rattle the coffee mugs. He'd spent three hours trying to debug a styling conflict in a React codebase that used styled-components, and somewhere between the nested template literals and the dynamically injected class names, he'd lost the thread entirely. "CSS was never the problem," he said, to no one in particular. I wasn't sure I agreed with him then. I'm not entirely sure I disagree with him now.

The CSS in JavaScript frameworks debate is one of those arguments that refuses to die because it's never really been about CSS. It's about philosophy. It's about what we believe the web is for, who gets to build it, and whether the instinct to unify everything under one language is wisdom or hubris.

How a Simple Idea Became a Culture War

The origin story is familiar enough. Facebook engineers, wrestling with the particular chaos of a massive, fast-moving codebase, concluded that global CSS was fundamentally broken at scale. Christopher Chedeau's 2014 talk — the one that introduced the phrase "CSS in JS" to a wider audience — laid out seven problems with CSS: global namespace, dependencies, dead code elimination, minification, sharing constants, non-deterministic resolution, isolation. The slides were dry. The reaction was anything but.

The web community responded the way it usually does to a genuinely provocative idea: half the room was electrified, half was furious. Libraries proliferated almost immediately. Radium, Aphrodite, JSS, Emotion, styled-components — each one a slightly different answer to the same question of how to make styling feel like a first-class citizen in a component model. By the time styled-components hit version 3, it had become something close to a default choice for a certain kind of React developer, the kind who thought in components all the way down.

And for a while, the argument felt settled — or at least exhausted. CSS-in-JS had won a significant portion of the ecosystem. Then Tailwind arrived and scrambled everything again.

Tailwind Changed the Terms of the Argument

Utility-first CSS is not CSS-in-JS. That distinction matters, and it gets blurred constantly in the broader debate. Tailwind keeps styles in markup — a different kind of co-location than the JavaScript-embedded approach — and it leans on a build step to strip unused classes rather than runtime injection. But it provoked the same emotional responses, the same tribal lines, because it challenged the same underlying assumption: that separation of concerns meant keeping your HTML, CSS, and JavaScript in different files.

I remember reading Adam Wathan's essay "CSS Utility Classes and 'Separation of Concerns'" and feeling genuinely unsettled by how persuasive it was. He argued that the traditional model wasn't really separating concerns at all — it was separating technologies, which is a different thing. Your CSS was still deeply coupled to your HTML; you'd just hidden the coupling behind a layer of indirection. The classes in your stylesheet knew exactly what your markup looked like. They had to.

That argument didn't end the debate. It deepened it. Because now there were multiple schools of CSS-in-JS thought, a resurgent utility-CSS movement, and a vocal contingent of developers who had never stopped writing plain old stylesheets and wanted everyone to know it.

The Performance Question That Won't Sit Still

For a few years, the technical criticism of CSS-in-JS focused on runtime cost. Injecting styles via JavaScript means the browser has to parse and execute JS before it can paint anything styled. On slow connections and low-end devices — the ones that most of the world actually uses — that overhead is real. Sam Magura's widely-shared 2022 post about Emotion's performance impact at Spot, and the team's subsequent migration away from CSS-in-JS, felt like a turning point. Here was a production team with real data saying the tradeoffs had shifted.

The CSS-in-JS community didn't collapse. It adapted. Zero-runtime approaches — Vanilla Extract, Linaria, StyleX from Meta — extracted the developer experience benefits of co-located, typed styles while moving the actual style generation to build time. The output is static CSS. The runtime cost drops toward zero. This felt, to me, like a genuine maturation of the idea rather than a retreat from it.

But the performance question was never purely technical. It was also political, in the small-p sense. Developers who had always been skeptical of CSS-in-JS used the performance data as a cudgel, and sometimes fairly so. The web has a long history of adding complexity in the name of developer experience and then discovering, years later, that the people who paid the price were users on the margins — slower phones, patchy networks, countries where data is expensive. That's a serious moral argument, not just an aesthetic preference.

What the Debate Is Actually About

Here's the thing I've come to believe after watching this argument cycle through its phases: the CSS in JavaScript frameworks debate is a proxy for a deeper disagreement about who the web is optimized for.

One camp believes the web's primary virtue is its resilience — the fact that a plain HTML file with a linked stylesheet loads on almost anything, degrades gracefully, and doesn't require a build pipeline. CSS, in this view, is a feature, not a limitation. Its cascade and inheritance are tools, not bugs. The people who hold this view tend to be suspicious of the entire modern JavaScript framework ecosystem, not just its styling conventions.

The other camp believes the web's primary virtue is its reach — the fact that it can deliver arbitrarily complex applications to anyone with a browser. For them, the question isn't whether to use a framework but which one, and styling is just another engineering problem to be solved with the best available tools. If those tools happen to live inside JavaScript files, so be it.

Both camps are right about something important. The web is resilient and it can be a platform for complex software. The problem is that the tooling choices that optimize for one tend to work against the other, and most real projects live somewhere in the middle, making uncomfortable compromises in both directions.

I've shipped projects in plain CSS, in CSS Modules, in styled-components, and in Tailwind. Each one taught me something different about the gap between what a technology promises and what it delivers at 11 PM when a deadline is close and a layout is broken in Safari. Plain CSS taught me the cascade. CSS Modules taught me that scoping is genuinely valuable. Styled-components taught me that co-location is seductive and occasionally treacherous. Tailwind taught me that constraints can be liberating until they aren't.

The Question the Next Generation Will Have to Answer

The current moment is genuinely interesting. React Server Components have complicated the CSS-in-JS picture in new ways — runtime injection doesn't play nicely with server rendering, which is part of why Meta built StyleX the way they did. The CSS language itself has finally caught up on some fronts: native nesting, cascade layers, container queries, and the :has() selector have arrived in browsers without a build step in sight. Some of the problems that CSS-in-JS was invented to solve are now solvable in CSS itself.

Does that mean the debate is over? I doubt it. Developers who fell in love with the component model will keep wanting their styles to live next to their logic. Developers who distrust JavaScript's grip on the web will keep making the case for the platform's native capabilities. And somewhere in the middle, the rest of us will keep making pragmatic choices and then writing blog posts defending them with more conviction than we actually feel.

That engineer who slammed his laptop shut eventually became a convert to CSS Modules — not because they're perfect, but because they were the least surprising option for his team. He'd tell you that's not a philosophy. I'd argue it's the only honest one.

The CSS in JavaScript frameworks debate will outlast any particular library because it keeps asking a question the industry hasn't fully answered: when we make the web easier to build, are we making it better for the people who use it, or just for ourselves?