Runtime Regret: The Performance Price Tag Hiding Inside Your CSS-in-JS Setup
There's a particular kind of technical debt that doesn't feel like debt when you're taking it on. CSS-in-JS was one of those moves — it felt like progress. Colocated styles, dynamic theming, no class name collisions, TypeScript autocomplete on your design tokens. What's not to love?
Quite a bit, it turns out. And the bill doesn't show up in your codebase. It shows up in your users' browsers.
What's Actually Happening at Runtime
Here's the part most CSS-in-JS tutorials gloss over: a significant chunk of these libraries generate your stylesheets at runtime, inside the JavaScript thread. Every time a component mounts, the library has to evaluate your style definitions, hash them, inject <style> tags into the document, and track what's already been inserted to avoid duplication.
That's not free. On a MacBook Pro with a fast connection, you'll never notice. But your users aren't all on MacBook Pros with fast connections. Mid-range Android devices — which represent a huge slice of real-world web traffic in the US and globally — have significantly slower JavaScript execution speeds. What feels instant in development can feel sluggish in production.
The memory overhead is real too. Libraries like styled-components and Emotion maintain in-memory caches of generated class names and style rules. In a large application with hundreds of styled components, that cache grows. Combine it with the cost of repeated style recalculation on re-renders, and you've got a slow leak that's hard to diagnose because it rarely shows up as a single obvious bottleneck.
The Parse Time Problem Nobody Talks About
Before your styles even run, they have to be parsed. With traditional CSS or CSS Modules, the browser handles style parsing natively — it's optimized, off the main thread, and fast. With runtime CSS-in-JS, you're asking JavaScript to do that job instead.
Every kilobyte of style logic bundled into your JS payload adds to parse and execution time. And unlike CSS files, JavaScript can't be parsed in parallel with HTML rendering. It blocks. That's a meaningful distinction when you're trying to hit a Time to Interactive target or keep your Lighthouse scores respectable.
Serious performance audits at companies like Atlassian and Airbnb have documented measurable gains from migrating away from runtime CSS-in-JS. These aren't theoretical concerns — they're measured milliseconds that show up in Core Web Vitals.
When the Tradeoff Still Makes Sense
None of this means CSS-in-JS is always the wrong call. Context matters a lot here.
If you're building an internal dashboard used by a few hundred employees on corporate laptops with fast enterprise WiFi, the runtime cost is probably negligible. Developer velocity, consistency across a large team, and tight integration with your design system might easily outweigh the performance concerns.
Same goes for highly dynamic UI where styles genuinely need to change based on JavaScript state in ways that are awkward to express in static CSS. There are real use cases where the colocation and dynamism of CSS-in-JS is the right tool.
The mistake is reaching for it by default — because it's familiar, because the last project used it, because the documentation is good. That's how you end up with runtime overhead that nobody intentionally signed off on.
What the Alternatives Actually Look Like Today
The good news is the alternatives have gotten genuinely good. CSS Modules give you scoped styles without any runtime cost. You get local class names, zero collision risk, and full static extraction — the browser gets plain CSS, the way it likes it.
Vanilla CSS with custom properties has also matured into a surprisingly capable system for dynamic theming. You can update a handful of CSS variables at the component level and get behavior that used to require a JavaScript style injection.
And then there's the middle ground: build-time CSS-in-JS tools like Linaria, vanilla-extract, or Panda CSS. These give you a TypeScript-friendly, component-scoped styling experience while extracting everything to static CSS at build time. You get much of the developer ergonomics without the runtime cost. If you're currently on a runtime solution and feeling the performance pressure, this category is worth a serious look.
Making the Call for Your Project
Here's a practical framework. Start by asking whether your styles actually need to be dynamic at runtime, or whether they just feel like they do. Most theming requirements can be handled with CSS variables. Most conditional styles can be handled with class toggling. If the answer is genuinely yes — you need JavaScript-evaluated style logic at render time — then a runtime library might be justified.
If the answer is no, or mostly no, you're probably paying a performance tax for a convenience you don't fully need.
Run a quick audit: open DevTools, throttle to a mid-tier mobile device, and profile your app's initial render. Look for style injection in the flame graph. Check your JavaScript bundle for how much weight your styling library is adding. Look at your Interaction to Next Paint scores on real devices using Chrome UX Report data.
The numbers will tell you what the documentation won't.
CSS-in-JS solved real problems when it arrived. It's not going away, and it doesn't need to. But treating it as the default choice rather than a considered one is how performance debt quietly accumulates — one styled component at a time.