The State of CSS-in-JS in 2026: Is It Dead?
TL;DR
CSS-in-JS isn't dead — but the runtime model is. Styled-components (~7M weekly downloads) and Emotion (~9M) still dominate by download count, but most are from legacy codebases. New projects in 2026 overwhelmingly choose Tailwind CSS (~12M), CSS Modules, or zero-runtime CSS-in-JS tools like Panda CSS and StyleX. The shift is clear: runtime CSS injection (bad for React Server Components, adds KB to bundles, blocks rendering) is being replaced by build-time extraction.
Key Takeaways
- Emotion: ~9M weekly downloads — peaked in 2023, declining for new projects
- styled-components: ~7M downloads — RSC-incompatible, v6 had migration pain
- Tailwind CSS: ~12M downloads — the winner; utility-first now the default DX
- Panda CSS: ~200K downloads — zero-runtime, TypeScript-first, RSC-compatible
- StyleX: ~100K downloads — Meta's build-time CSS, atomic classes, performance-first
What Happened to CSS-in-JS?
The death knell arrived with React Server Components in 2023-2024. Runtime CSS-in-JS libraries like styled-components and Emotion inject styles via JavaScript at runtime — which means:
- Server Components can't use them — RSC runs on the server, no browser context for style injection
- Bundle size — styled-components adds ~50KB gzipped; Emotion adds ~15KB
- Flash of unstyled content — server renders HTML, then client re-injects styles
- Streaming incompatibility — style injection conflicts with React 18's streaming SSR
By 2025, the Emotion and styled-components teams explicitly acknowledged these limitations. styled-components v6 dropped some workarounds that broke many codebases mid-migration.
The Winners: What's Replacing CSS-in-JS
1. Tailwind CSS (The Dominant Replacement)
// Tailwind — utility classes, zero runtime
function PackageCard({ pkg }: { pkg: Package }) {
return (
<div className="rounded-xl border border-gray-200 bg-white p-6 shadow-sm
hover:shadow-md transition-shadow duration-200
dark:border-gray-700 dark:bg-gray-800">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{pkg.name}
</h3>
<span className={`rounded-full px-2 py-0.5 text-xs font-medium
${pkg.health >= 80 ? 'bg-green-100 text-green-700' :
pkg.health >= 60 ? 'bg-yellow-100 text-yellow-700' :
'bg-red-100 text-red-700'}`}>
{pkg.health}/100
</span>
</div>
<p className="mt-2 text-sm text-gray-500 line-clamp-2">{pkg.description}</p>
</div>
);
}
Tailwind outputs a static CSS file at build time — no runtime, no JS, works in RSC, works everywhere.
2. CSS Modules (The Quiet Standard)
// CSS Modules — scoped CSS, zero runtime, RSC-compatible
// PackageCard.module.css
// .card { border-radius: 12px; background: white; ... }
// .health-good { color: green; }
// .health-bad { color: red; }
import styles from './PackageCard.module.css';
function PackageCard({ pkg }) {
return (
<div className={styles.card}>
<span className={pkg.health >= 80 ? styles['health-good'] : styles['health-bad']}>
{pkg.health}/100
</span>
</div>
);
}
CSS Modules are fully supported in Next.js, Vite, and every major bundler. Zero runtime, true scoping.
3. Panda CSS (Zero-Runtime CSS-in-JS)
// Panda CSS — write like CSS-in-JS, output like Tailwind
import { css } from '../styled-system/css';
import { stack, hstack } from '../styled-system/patterns';
function PackageCard({ pkg }) {
return (
<div className={css({
rounded: 'xl',
border: '1px solid',
borderColor: 'gray.200',
bg: 'white',
p: 6,
shadow: 'sm',
_hover: { shadow: 'md' },
_dark: { borderColor: 'gray.700', bg: 'gray.800' },
})}>
<div className={hstack({ justify: 'space-between' })}>
<h3 className={css({ textStyle: 'lg', fontWeight: 'semibold' })}>
{pkg.name}
</h3>
</div>
</div>
);
}
// At build time, Panda extracts static CSS — no runtime JS
// Output: compiled atomic classes like Tailwind
Panda CSS is the spiritual successor to CSS-in-JS for teams who loved the API but need RSC compatibility.
4. StyleX (Meta's Approach)
// StyleX — build-time, atomic, predictable
import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({
card: {
borderRadius: '12px',
backgroundColor: { default: 'white', '@media (prefers-color-scheme: dark)': '#1f2937' },
padding: '24px',
boxShadow: { default: '0 1px 3px rgba(0,0,0,0.1)', ':hover': '0 4px 6px rgba(0,0,0,0.1)' },
},
healthGood: { color: 'green' },
healthBad: { color: 'red' },
});
function PackageCard({ pkg }) {
return (
<div {...stylex.props(styles.card)}>
<span {...stylex.props(pkg.health >= 80 ? styles.healthGood : styles.healthBad)}>
{pkg.health}/100
</span>
</div>
);
}
StyleX outputs atomic CSS at build time. Meta uses it to style facebook.com — performance at extreme scale.
Download Trend Analysis
styled-components weekly downloads:
2023: 8.5M → 2024: 7.5M → 2026: 6.8M (-20% over 3 years)
Emotion weekly downloads:
2023: 10M → 2024: 9.5M → 2026: 9.1M (-9%, more stable due to MUI)
Tailwind CSS weekly downloads:
2023: 6M → 2024: 9M → 2026: 12M (+100% over 3 years)
Panda CSS:
2024: 80K → 2026: 200K (+150%)
Vanilla Extract:
2023: 400K → 2026: 450K (stable)
The transition is happening — just slower than predicted because legacy codebases don't rewrite themselves.
When CSS-in-JS Still Makes Sense
Not everything is RSC. CSS-in-JS is still the right choice for:
- Design systems — Material UI (Emotion) and Chakra UI (Emotion) are still widely used
- Dynamic theming at runtime — Changing themes based on user preferences stored in DB
- Existing codebases — The cost of rewriting Emotion to Tailwind is rarely worth it
- React Native — StyleSheet API is CSS-in-JS-like; StyleSheet.create is idiomatic
- Client-only apps (no SSR) — Runtime injection is fine if you don't SSR/RSC
The Decision Tree for 2026
New project?
├── Uses React Server Components → Tailwind or CSS Modules
├── Heavy theming / design system → Panda CSS or StyleX
├── Familiar with Tailwind → Tailwind
└── Small team, scoped styles → CSS Modules
Existing project?
├── styled-components / Emotion working fine → Keep it
├── Migrating to RSC → Evaluate Tailwind or Panda CSS
└── Performance issues → Profile first; consider Tailwind migration
Comparison Table
| Library | Runtime | RSC Support | Bundle Add | DX | Adoption Trend |
|---|---|---|---|---|---|
| styled-components | ✅ Yes | ❌ Limited | ~50KB | ⭐⭐⭐⭐ | ↓ Declining |
| Emotion | ✅ Yes | ❌ Limited | ~15KB | ⭐⭐⭐⭐ | ↓ Slight decline |
| Tailwind CSS | ❌ None | ✅ Perfect | ~10KB (CSS) | ⭐⭐⭐⭐⭐ | ↑ Rising fast |
| Panda CSS | ❌ None | ✅ Perfect | ~5KB (CSS) | ⭐⭐⭐⭐ | ↑ Rising |
| StyleX | ❌ None | ✅ Perfect | ~5KB (CSS) | ⭐⭐⭐ | ↑ Rising |
| CSS Modules | ❌ None | ✅ Perfect | 0 (CSS) | ⭐⭐⭐ | → Stable |
| Vanilla Extract | ❌ None | ✅ Perfect | ~5KB | ⭐⭐⭐ | → Stable |
Migration Guide: Styled-Components to Tailwind
Migrating from styled-components to Tailwind is the most common CSS migration path in 2026. Here's how teams do it without disrupting feature work.
The Strategy: Island-by-Island
Don't attempt a full rewrite. Instead, migrate components as you touch them for other reasons — adding a feature, fixing a bug, refactoring. This "strangler fig" approach means the migration completes over 2-4 months without a dedicated migration sprint.
Step 1: Install Tailwind alongside styled-components. Both can coexist. Tailwind classes work on any element, regardless of whether that element is otherwise styled with styled-components.
npm install tailwindcss postcss autoprefixer
npx tailwindcss init
Step 2: Configure Tailwind content paths to include your source files. Run npx tailwindcss build and verify the output CSS includes utilities.
Step 3: Create a migration guide for your team. The most common translation patterns:
styled-components → Tailwind
// Layout
display: flex; justify-content: space-between → flex justify-between
display: grid; grid-template-columns: 1fr 1fr → grid grid-cols-2
// Spacing
padding: 16px → p-4
margin: 8px 16px → my-2 mx-4
// Typography
font-size: 14px; font-weight: 500 → text-sm font-medium
color: #6B7280 → text-gray-500
// Borders
border-radius: 8px; border: 1px solid #E5E7EB → rounded-lg border border-gray-200
// Effects
box-shadow: 0 1px 3px rgba(0,0,0,0.1) → shadow-sm
Step 4: Handle dynamic styles — the hard part. Styled-components excels at dynamic styling based on props. In Tailwind, you handle this differently:
// Before: styled-components with dynamic props
const Badge = styled.span<{ variant: 'success' | 'warning' | 'error' }>`
background: ${({ variant }) => ({
success: '#dcfce7',
warning: '#fef9c3',
error: '#fee2e2',
}[variant])};
color: ${({ variant }) => ({
success: '#166534',
warning: '#854d0e',
error: '#991b1b',
}[variant])};
`;
// After: Tailwind with lookup table (use cva for complex variants)
import { cva } from 'class-variance-authority';
const badge = cva('rounded-full px-2 py-0.5 text-xs font-medium', {
variants: {
variant: {
success: 'bg-green-100 text-green-800',
warning: 'bg-yellow-100 text-yellow-800',
error: 'bg-red-100 text-red-800',
},
},
});
function Badge({ variant, children }) {
return <span className={badge({ variant })}>{children}</span>;
}
cva (Class Variance Authority) is the standard companion to Tailwind for variant-driven component APIs, and is used internally by shadcn/ui.
Step 5: Remove styled-components once no components reference it. Run grep -r "styled-components" src/ to verify. Remove the package and the ThemeProvider wrapper.
Common Mistakes When Leaving CSS-in-JS
Teams migrating away from CSS-in-JS often make a few predictable errors.
Mistake 1: Unsafe Dynamic Class Names in Tailwind
Tailwind's purge/JIT system scans source files for class names as strings. Dynamically constructing class names from variables breaks this.
// ❌ Tailwind won't include these classes — they'll be purged
const color = 'red';
const size = '4';
<div className={`text-${color}-500 p-${size}`} />
// ✅ Use complete class strings that Tailwind can detect
const colorClasses = {
red: 'text-red-500',
blue: 'text-blue-500',
green: 'text-green-500',
};
<div className={`${colorClasses[color]} p-4`} />
Mistake 2: Keeping Emotion as a Global StylesProvider During Migration
Some teams add Tailwind while keeping Emotion's global cache and GlobalStyles as a transition measure. The result is two style systems fighting for specificity. Tailwind utilities use no specificity tricks — they rely on source order. Emotion's injected styles often win due to injection order, causing Tailwind classes to have no visible effect. Decide upfront which system owns each component.
Mistake 3: Not Using the Tailwind Config for Brand Colors
Teams migrating from a design system built on styled-components tokens often hardcode hex values in Tailwind classes (text-[#3B82F6]) rather than extending the theme. This defeats maintainability.
// tailwind.config.js — define your design tokens once
theme: {
extend: {
colors: {
brand: {
primary: 'hsl(217, 91%, 60%)',
secondary: 'hsl(217, 91%, 45%)',
},
},
},
},
Then use text-brand-primary everywhere — refactoring the color later is a one-line change.
Performance Impact: Runtime CSS-in-JS at Scale
The performance cost of runtime CSS-in-JS is measurable but context-dependent. For most small-to-medium applications, it's not the primary performance bottleneck. Where it becomes significant:
Large initial renders. When a page server-renders 500+ styled components, the style injection phase on client hydration can block the main thread for 200-400ms on mid-range mobile devices. This directly worsens INP (Interaction to Next Paint) scores.
Frequent re-renders with dynamic styles. If a styled component with ${props => props.color} interpolation renders inside a virtualized list, each row render triggers a style recalculation. Libraries like Emotion cache these aggressively, but cache invalidation has its own cost.
Bundle size on slow connections. Emotion's ~15KB and styled-components' ~50KB are not large by modern standards — but on 3G connections (still common in parts of the world), these are meaningful. Brotli compression helps: both libraries compress to roughly 1/4 their raw size.
The honest benchmark: switching from Emotion to Tailwind on a typical Next.js marketing site is unlikely to move your Lighthouse score more than 5-10 points. The bigger gains come from reducing JavaScript overall (server components, better code splitting) and from image optimization.
Where the switch genuinely matters: React Server Components. If your architecture depends on RSC for performance, runtime CSS-in-JS is not a choice — it's a hard blocker.
FAQ
Q: Is it worth migrating an existing styled-components codebase?
Usually not as a standalone project. The ROI is low if your app doesn't use RSC and isn't experiencing CSS-related performance issues. Migrate opportunistically — as you touch components, convert them. A forced migration sprint rarely pays back the engineering cost.
Q: Can styled-components ever support React Server Components?
The styled-components maintainers have acknowledged this is fundamentally incompatible with their runtime injection model. A hypothetical styled-components v7 would need to be a completely different tool. Panda CSS and StyleX are the continuations of the CSS-in-JS idea built for the RSC era.
Q: What about CSS custom properties (variables) instead of CSS-in-JS for theming?
CSS custom properties are an increasingly popular alternative for dynamic theming. Define your tokens as CSS variables in :root, switch themes by swapping a data attribute, and use those variables in Tailwind (text-[var(--color-brand)]). This approach has zero runtime JavaScript cost and works perfectly with RSC.
Q: Is Vanilla Extract worth adopting for new projects?
Vanilla Extract is a solid choice — zero runtime, TypeScript-first, built-in theming via CSS variables. Its adoption has plateaued rather than grown (stable at ~450K weekly downloads) because Tailwind captured most of the market. Choose Vanilla Extract if you want CSS-in-TypeScript with full design token support and don't want Tailwind's utility-class model.
Verdict: CSS-in-JS isn't dead, but the runtime model is on life support for new projects. Tailwind won the DX battle. Build-time CSS-in-JS (Panda, StyleX, Vanilla Extract) is the future for teams that want CSS-in-JS ergonomics with modern constraints.
Compare CSS library package health on PkgPulse.
See also: Emotion vs styled-components and vanilla-extract vs Panda CSS vs Tailwind, Emotion vs styled-components in 2026: CSS-in-JS Endgame.
See the live comparison
View styled components vs. emotion on PkgPulse →