Skip to main content
All articles
Theming May 1, 2026 7 min read

Zero-Runtime CSS Theming: Why CSS Variables Beat CSS-in-JS

JavaScript theme providers ship extra JS, cause re-renders, and create hydration mismatches. CSS custom properties do the same job with zero runtime cost.

By Ninna UI Team


For years, CSS-in-JS was the standard for theming React apps. Libraries like Emotion and styled-components gave us dynamic styling, scoped classes, and theme objects. But the cost was real: extra JavaScript in the bundle, runtime style computation, and hydration mismatches on SSR.

The cost of CSS-in-JS theming

  • The theme object and runtime ship in your JS bundle (typically 5-15KB gzipped).
  • Theme changes can trigger React re-renders across the component tree.
  • On SSR, there's a window where server-rendered styles and client styles disagree — the flash of unstyled or wrong-styled content.
  • Style computation happens at runtime — every render potentially generates new CSS.

CSS custom properties: the zero-runtime alternative

CSS custom properties (variables) give you dynamic theming without any JavaScript. The browser is the runtime — switching themes is just changing which variables apply:

:root {
  --color-primary: oklch(0.55 0.22 280);
  --color-primary-content: oklch(0.98 0.01 280);
}

[data-theme="dark"] {
  --color-primary: oklch(0.72 0.18 280);
  --color-primary-content: oklch(0.15 0.02 280);
}

No provider, no context, no re-render. The browser handles the switch natively. This is exactly how Ninna UI ships theming.

What you lose (and it's less than you think)

CSS-in-JS gave us: dynamic styles based on props (use className with conditional logic instead), TypeScript autocomplete on theme keys (use Tailwind's built-in types), and co-located styles (use data-slot CSS targets). None of these require a JavaScript runtime.

The Tailwind v4 factor

Tailwind CSS v4 made CSS-first theming practical. The @theme directive maps CSS variables to utility classes, so you get bg-primary, text-success, border-warning — all driven by CSS custom properties. No config file, no JavaScript.

@import "tailwindcss";
@import "@ninna-ui/core/theme/presets/default.css";
/* That's it. 8 semantic colors, dark mode, all via CSS. */

The verdict

For 2026 projects, zero-runtime CSS theming is the better default. You get smaller bundles, faster hydration, no theme flash, and simpler architecture. CSS-in-JS still has niche use cases, but for most apps, CSS variables + Tailwind v4 is the way.


Build it with Ninna UI

Accessible React components, CSS-only theming, Tailwind v4 native.

Get Started

Keep reading