CSSCSS-in-JS Overview

CSS-in-JS Overview

CSS-in-JS is an approach where component styles are written directly in JavaScript (or TypeScript) files, usually right next to the component they belong to, instead of in separate .css files. Libraries like styled-components and Emotion are the most widely used implementations, and both work by generating real CSS at build time or runtime and injecting it into the page, with scoped, auto-generated class names attached to each component.
This site uses CSS-in-JS right now
This tutorial site is built with MUI, which uses Emotion under the hood for its styling engine. Every sx prop and styled MUI component you see rendered on this page went through a CSS-in-JS pipeline before it reached the browser — it's not a purely theoretical topic here.
What it looks like

The exact syntax differs by library, but the core idea is always the same: a tagged template literal or a style object lives inside the component file, and the library turns it into a real CSS rule with a unique, collision-free class name.

JSX
// styled-components
import styled from 'styled-components'

const Card = styled.div`
  padding: 1rem;
  border-radius: 8px;
  background: ${(props) => (props.$featured ? '#fff8e1' : '#ffffff')};
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);

  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.16);
  }
`

function ProductCard({ featured, children }) {
  return <Card $featured={featured}>{children}</Card>
}

JSX
// Emotion, using the css prop
import { css } from '@emotion/react'

function ProductCard({ featured, children }) {
  return (
    <div
      css={css`
        padding: 1rem;
        border-radius: 8px;
        background: ${featured ? '#fff8e1' : '#ffffff'};
      `}
    >
      {children}
    </div>
  )
}
Notice how the styling logic in both examples reads a JavaScript prop (featured) directly — that's the core capability CSS-in-JS is built around, and it's difficult to replicate as cleanly with a plain .css file.
Why teams adopt it

Benefit

What it means in practice

Co-location

A component and its styles live in the same file, so there is no separate stylesheet to keep in sync, hunt down, or accidentally leave orphaned when the component is deleted.

Dynamic styles from props/state

Styles can read component props or state directly, producing values a static CSS file cannot compute — no need to toggle a class name from JS just to swap a color.

Automatic scoping

Generated class names are unique per style block, which removes the naming-collision problem that motivated methodologies like BEM in a plain-CSS world.

Dead style elimination

Because styles are tied to component code, unused components and their styles tend to get removed together rather than leaving stale CSS behind.

The trade-offs

None of this is free. Traditional, runtime CSS-in-JS libraries parse template literals or style objects and inject <style> tags into the page while the app is running, which adds real JavaScript execution cost on every render pass that touches styles — worse on low-end devices, and worse the more dynamic the styles are.

  • Runtime performance cost — style computation and injection happen in the browser, on the main thread, competing with everything else your app is doing.

  • Extra build tooling — babel/SWC plugins, bundler configuration, and sometimes server-side rendering extraction steps are needed to avoid a flash of unstyled content.

  • Larger JavaScript bundles — the styling library itself, plus the serialized style logic for every component, ships as JS rather than a separately cacheable CSS file.

  • Debugging friction — generated class names in DevTools are often opaque hashes rather than something you wrote, though most libraries offer a debug mode that improves this.

Zero-runtime and compile-time approaches
A newer generation of tools (such as vanilla-extract, Linaria, and Emotion's own compiler-assisted mode) tries to get the co-location and type-safety benefits of CSS-in-JS while extracting everything to static .css files at build time, so nothing runs in the browser at render time. This "zero-runtime CSS-in-JS" category is still maturing and doesn't yet support every dynamic-styling pattern the runtime libraries do.
This is a genuinely unsettled area of frontend architecture
Unlike, say, whether to use box-sizing: border-box, there is real, ongoing disagreement among experienced teams about whether CSS-in-JS, CSS Modules, or utility-first CSS is the best default choice for a given project. The right answer depends on team size, rendering strategy (especially server-side rendering performance budgets), and how dynamic the styling genuinely needs to be — treat any strong one-size-fits-all opinion you read online with some skepticism.
Related pages
See CSS Modules for a framework- agnostic way to get real, scoped CSS files without a JS runtime, and Component-Scoped CSS for the broader architectural goal all of these approaches share.