Skip to main content

CSS-in-JS vs Traditional CSS

Answer

CSS-in-JS is an approach where CSS is written within JavaScript files, providing component-scoped styles. It contrasts with traditional CSS approaches like separate stylesheets or CSS Modules.

Approach Comparison

Traditional CSS

/* styles.css */
.button {
background: blue;
color: white;
padding: 10px 20px;
}

.button:hover {
background: darkblue;
}

.button.disabled {
opacity: 0.5;
cursor: not-allowed;
}
<link rel="stylesheet" href="styles.css" />
<button class="button">Click me</button>

Pros:

  • Browser caching
  • No runtime overhead
  • Works without JavaScript
  • Familiar to all developers

Cons:

  • Global namespace (name collisions)
  • Dead code is hard to detect
  • No dynamic values from JavaScript

CSS Modules

/* Button.module.css */
.button {
background: blue;
color: white;
}

.disabled {
opacity: 0.5;
}
import styles from "./Button.module.css";

function Button({ disabled }) {
return (
<button className={`${styles.button} ${disabled ? styles.disabled : ""}`}>
Click me
</button>
);
}

Pros:

  • Scoped class names (no collisions)
  • Still separate CSS files
  • Browser caching works
  • No runtime overhead

Cons:

  • Still need conditional class logic
  • Limited dynamic styling
  • Separate file per component

CSS-in-JS Libraries

Styled Components

import styled from 'styled-components';

const Button = styled.button`
background: ${props => props.primary ? 'blue' : 'gray'};
color: white;
padding: 10px 20px;
opacity: ${props => props.disabled ? 0.5 : 1};

&:hover {
background: ${props => props.primary ? 'darkblue' : 'darkgray'};
}
`;

// Usage
<Button primary>Click me</Button>
<Button disabled>Disabled</Button>

Emotion

/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";

const buttonStyle = css`
background: blue;
color: white;
padding: 10px 20px;
`;

function Button() {
return <button css={buttonStyle}>Click me</button>;
}

// Or with styled API
import styled from "@emotion/styled";

const Button = styled.button`
background: blue;
`;

Comparison Table

FeatureTraditional CSSCSS ModulesCSS-in-JS
ScopingGlobalScopedScoped
Dynamic stylesLimitedLimitedFull
Runtime costNoneNoneYes
Bundle sizeSeparateHashedIn JS bundle
Dead code eliminationHardBetterAutomatic
ThemingVariablesVariablesProps/Context
Learning curveLowLowMedium
SSR complexitySimpleSimpleMore complex

Modern Alternatives

Tailwind CSS (Utility-First)

<button
class="bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded disabled:opacity-50"
>
Click me
</button>

Pros:

  • No custom CSS to write
  • Consistent design system
  • Small production bundle (purged)

Cons:

  • HTML can become verbose
  • Learning utility classes
  • Less readable for complex styles

Zero-Runtime CSS-in-JS

// Vanilla Extract (zero runtime)
import { style } from "@vanilla-extract/css";

export const button = style({
background: "blue",
color: "white",
":hover": {
background: "darkblue",
},
});

Performance Considerations

CSS-in-JS Performance Issues:

  • Style parsing at runtime
  • CSS injection on every render (if not optimized)
  • Larger JavaScript bundles
  • Hydration mismatches in SSR

Mitigations:

  • Use zero-runtime libraries (Vanilla Extract, Linaria)
  • Static extraction when possible
  • Memoization of style objects

When to Use What

ScenarioRecommendation
Static site/contentTraditional CSS or Tailwind
Component libraryCSS Modules or CSS-in-JS
Design systemTailwind or CSS Variables
Highly dynamic UICSS-in-JS
Performance criticalZero-runtime or Traditional
Team familiarityWhat team knows best

Key Points

  • Traditional CSS: Simple, cacheable, global scope issues
  • CSS Modules: Scoped, no runtime, still separate files
  • CSS-in-JS: Dynamic, collocated, runtime overhead
  • Tailwind: Utility, fast development, verbose HTML
  • Zero-runtime: Best of both (scope + performance)
  • Choose based on project needs, not trends