{"id":"c712wkua0ug1jm7","title":"CSS-in-JS Runtime Cost: When Global CSS Wins","slug":"css-in-js-runtime-cost","summary":"Runtime styling trades build simplicity for main-thread work. I measure injection, hydration, and cache behavior before picking CSS-in-JS.","imageUrl":"https://briancrabtree.me/images/journal-css-in-js-runtime-cost.webp","category":"CSS","date":"2026-05-25T18:00:00.000Z","featured":false,"likes":19,"author":"Brian Crabtree","content":"<h2>What CSS-in-JS buys and spends</h2>\n\n<p>Colocated styles, dynamic props, and automatic unique class names help large component teams. You spend JavaScript to generate rules, insert stylesheets or style tags at runtime, and sometimes fight specificity with duplicated selectors.</p>\n\n<p>styled-components and emotion peaked in design-system-heavy React apps. Build-time alternatives like vanilla-extract and Linaria moved cost to compile time for good reason.</p>\n\n<p>Critical path CSS extraction tools for emotion exist but add build config. Teams skip and pay runtime cost. Stylelint on template literals exists but adoption is low; discipline stays manual.</p>\n\n<pre><code>/* Prefer tokens in vanilla CSS over runtime-injected rules */\n:root { --accent: 204 255 0; }\n.btn { background: rgb(var(--accent)); }</code></pre>\n\n<h2>Performance mechanics</h2>\n\n<p>Each render can regenerate class names and rules unless memoized carefully. SSR requires critical CSS extraction or flash of unstyled content. Multiple instances bloat memory on long sessions.</p>\n\n<p>Lighthouse sees larger JS and longer script evaluation. INP suffers when interaction triggers style recalculation across huge injected sheets.</p>\n\n<p>Multiple theme providers nested in micro-frontends multiply injection cost. Single provider at shell root only. Fela and other libraries have different injection models; compare before blanket advice.</p>\n\n<h2>SSR and streaming headaches</h2>\n\n<p>Hydration must match server-generated class names. Theme changes via context re-render trees. I have debugged midnight production issues from missing StyleSheetManager on a portal route.</p>\n\n<p>Static CSS files cache across navigations; runtime CSS reinjects per route mount in SPA unless global cache layer exists.</p>\n\n<p>Snapshot tests for styled components churn on class hash changes. Visual regression on real DOM is calmer. RSC might colocate CSS files without runtime injection; follow framework guidance when on Next 15+.</p>\n\n<h2>When I still use it</h2>\n\n<p>White-label SaaS with per-tenant theme objects mapped to props can justify runtime styling with strict budgets and tests. Open-source component libraries shipping to unknown hosts sometimes need isolation CSS-in-JS provides.</p>\n\n<p>Even then I explore CSS variables for theme dimensions and reserve runtime generation for truly dynamic values only.</p>\n\n<p>Linaria and similar zero-runtime options deserve evaluation before greenfield styled-components default. Storybook with CSS-in-JS needs same ThemeProvider wrapper as app or chromatic lies.</p>\n\n<h2>Migration stories</h2>\n\n<p>Extract tokens to :root, convert stable components to modules.css, delete unused prop interpolations. Measure bundle and INP weekly. Big-bang rewrite is rare; strangler on leaf components works.</p>\n\n<p>CSS modules with typed scss modules give DX without runtime; TypeScript codegen bridges gap.</p>\n\n<p>Unit tests snapshotting styles are brittle; assert computed style in jsdom for critical focus ring only.</p>\n\n<h2>Vanilla alternative on content sites</h2>\n\n<p>briancrabtree.me uses plain CSS files and custom properties. No style injection on journal read. Perf scores and maintainability improved versus the experimental CSS-in-JS pass years ago on a prototype.</p>\n\n<p>Runtime CSS-in-JS on emails is impossible anyway; shared tokens in plain CSS help both web and mail.</p>\n\n<p>Marketing one-pager in styled-components inside corporate React app is acceptable island if isolated.</p>\n\n<h2>Decision</h2>\n\n<p>If your styles are mostly static and theming is two modes, global CSS wins. If styles are functions of dozens of runtime props per row in a grid, compile-time or scoped CSS modules plus variables likely beat full runtime CSS-in-JS. Send your styled-components profile; I will quantify JS cost and propose escape path.</p>\n\n<p>I profile React commit phase before blaming CSS-in-JS alone. Often state shape is co-conspirator.</p>\n\n<p>Build time extraction to static CSS file is migration end state I recommend for content surfaces. For a related angle I keep coming back to, see <a href=\"/journal/tailwind-vs-vanilla-css-tradeoffs/\">Tailwind vs Vanilla CSS: Tradeoffs I Explain to Clients</a>.</p>","tags":["css-in-js","performance","styled-components"],"views":58}