In short
CSS-in-JS is a family of approaches, popularised by styled-components and Emotion, in which component styles are written in JavaScript alongside the component, scoped automatically to avoid clashes, and able to change with props and state. It fixed real problems with global CSS at the cost of a runtime that generates styles in the browser, which slows rendering and conflicts with React Server Components. Zero-runtime libraries such as vanilla-extract, Panda CSS and StyleX keep the authoring model and compile to static CSS. For most new projects in 2026, CSS modules, Tailwind or a zero-runtime library are the better default.
What it is
Instead of a stylesheet, styles are written in the same file as the component, in JavaScript, and the library turns them into CSS with generated, unique class names at render time. In styled-components:
const Button = styled.button`
padding: 0.75rem 1.25rem;
background: ${p => p.primary ? "#2e4bff" : "#eee"};
`;
The component and its styling are one unit; the class name is unique, so nothing leaks; and the style can depend on props, state and theme values passed through JavaScript.
The approach emerged around 2014 as React spread, and styled-components and Emotion became the dominant libraries by 2018. For several years it was the default in React applications.
What it fixed
Global CSS has no scoping. A .card rule written for one page affects every .card on the site; specificity wars follow; nobody dares delete a rule because nobody knows what uses it. Naming conventions such as BEM managed the problem by discipline. CSS-in-JS removed it by construction: styles belong to a component, cannot affect anything else, and are deleted with the component.
It also made dynamic styling natural, brought theming and design tokens into JavaScript where the rest of the application's logic lived, and let a component be moved between projects with its styles intact, which is the design-system portability argument.
What it cost
Runtime. The library runs in the browser, generating CSS rules as components render and injecting them into the document. That work happens on every page load and on every re-render of a dynamically styled component, on the main thread, and it adds to the JavaScript bundle. Measurements from library authors themselves found meaningful rendering overhead, particularly on slower devices, and the maintainers of styled-components and Emotion both published pieces acknowledging the cost.
Server rendering complexity. Styles generated in JavaScript must be extracted and inlined on the server to avoid a flash of unstyled content, which every library handled differently and none handled simply.
Server components. React Server Components render on the server with no client JavaScript and no access to the runtime that CSS-in-JS libraries need. Runtime CSS-in-JS does not work inside them, which was the moment many teams reconsidered; the Next.js App Router documentation steers users toward other approaches.
Debugging. Generated class names such as sc-bdfBwQ make the DOM harder to read and browser dev tools less useful.
Lock-in. Styles written in a library's syntax are harder to move than plain CSS.
The zero-runtime successors
A newer generation keeps the authoring experience and removes the browser cost by compiling styles to static CSS at build time: vanilla-extract, Panda CSS, StyleX from Meta, Linaria, and Pigment CSS from the MUI team. Styles are still co-located, scoped and typed; the output is an ordinary stylesheet; there is no runtime, so server components work and rendering is unaffected. Dynamic values are handled through CSS custom properties rather than regenerated rules. For teams that liked CSS-in-JS for its ergonomics, these are the migration path.
The alternatives
CSS modules scope class names per file at build time, with plain CSS syntax and no runtime. Supported by every framework, boring in the best sense, and the default many teams returned to.
Tailwind applies utility classes directly in markup, compiled to a small stylesheet; scoping is a non-issue because there are no component-level rules. The Tailwind-versus-CSS debate is about authoring taste more than performance.
Plain CSS with custom properties and modern features. Cascade layers, nesting, :has() and container queries have made well-organised plain CSS viable for large projects again, and it needs no tooling at all.
| Approach | Scoping | Runs in the browser? |
|---|---|---|
| Runtime CSS-in-JS | Automatic | Yes, a library generates rules |
| Zero-runtime successors | Automatic | No, extracted at build |
| CSS modules | Per file, at build | No |
| Tailwind | Utility classes in markup | No |
| Plain CSS, with cascade layers | By convention and layers | No |
Migrating away
Teams leaving a runtime library usually do it component by component rather than all at once. Extract the design tokens into CSS custom properties first, so both old and new styles read the same values. Convert the leaf components, buttons and inputs, to the new approach, then work outward, keeping the old library installed until the last component is gone. Visual regression tests catch the differences that a manual review misses. The migration is tedious rather than risky, and the bundle shrinks visibly as each component moves.
A decision guide for 2026
New React project: CSS modules or Tailwind by default, or a zero-runtime library if the team values typed, co-located styles. Anything using server components: not runtime CSS-in-JS. Existing styled-components or Emotion codebase that works: no urgent need to migrate unless performance or server components force it, and the zero-runtime libraries offer the least disruptive path when it does. Non-React projects: the question rarely arises; CSS modules or plain CSS.
CSS-in-JS was a correct answer to a real problem, and the problem is now solved by cheaper means. The habit it taught, styles that belong to components, survives in every approach that replaced it. If a codebase of yours is slow to render and its class names look like random strings, book a call; the migration is usually more mechanical than feared.
Common questions
Is CSS-in-JS bad for performance?
Runtime CSS-in-JS, such as styled-components and Emotion, generates styles in the browser on every render, which adds JavaScript weight and main-thread work and measurably slows rendering on slower devices. Zero-runtime libraries such as vanilla-extract, Panda and StyleX compile to static CSS and have no such cost.
Why do people say CSS-in-JS is dead?
Because its runtime cost became widely acknowledged, including by the maintainers of the major libraries, and because React Server Components cannot run a browser-side style engine, which made runtime CSS-in-JS incompatible with the direction of the React ecosystem. The authoring ideas survive in zero-runtime libraries; the runtime approach is in decline.
What should I use instead of styled-components?
CSS modules for plain, scoped CSS with no runtime; Tailwind for utility-class styling; or a zero-runtime library such as vanilla-extract, Panda CSS, StyleX or Pigment CSS if you want typed, co-located styles compiled to static CSS. All work with server components. Migration from styled-components is most direct to Pigment or Linaria.
Does CSS-in-JS work with React Server Components?
Runtime libraries do not, because server components render without client JavaScript and the libraries need a browser runtime to generate styles. Zero-runtime CSS-in-JS, CSS modules and Tailwind all work, since they produce static CSS at build time.
What problem did CSS-in-JS solve?
Global CSS scoping: rules leaking between components, specificity conflicts and stylesheets nobody dared delete from. CSS-in-JS scoped styles to components automatically, made dynamic styling and theming natural, and let components carry their styles between projects. Those benefits are now available without a runtime through CSS modules and zero-runtime libraries.