Zero-Runtime Theming with Tailwind CSS v4 and oklch
Why JavaScript theme providers cost you performance, and how CSS custom properties plus oklch colors give you dynamic theming with nothing to hydrate.
By Ninna UI Team
Most React component libraries theme in JavaScript: a provider holds a theme object, components read it through context, and styles are computed at runtime. It's flexible, but it costs you bundle size, a context re-render surface, and a hydration step that can flash the wrong theme. Tailwind CSS v4 makes a different approach practical — theming entirely in CSS.
The problem with JS theming runtimes
- The theme object and provider ship in your JS bundle.
- Theme changes can trigger React re-renders across the tree.
- On SSR, there's a window where the server theme and client theme disagree — the dreaded flash.
CSS custom properties are the runtime
Instead of a JS object, define your theme as CSS variables. The browser is the runtime — switching themes is just changing which variables apply, with zero JavaScript:
:root {
--color-primary: oklch(0.55 0.22 280);
}
[data-theme="dark"] {
--color-primary: oklch(0.72 0.18 280);
}Why oklch instead of HSL or hex
oklch is a perceptual color space: equal numeric steps look like equal visual steps to the human eye. That makes generating accessible, consistent palettes far easier than with HSL, where lightness is not perceptually uniform.
/* Perceptually even steps — great for scales */
--primary-400: oklch(0.70 0.18 280);
--primary-500: oklch(0.62 0.20 280);
--primary-600: oklch(0.55 0.22 280);This is exactly how Ninna UI ships theming: one CSS import defines oklch variables, dark mode is a CSS selector, and there is no provider to render or hydrate.
Dynamic theming without JavaScript
Need user-selectable themes? Toggle a data attribute on <html>. The only JavaScript involved is setting one attribute — no provider, no context, no re-render storm:
document.documentElement.dataset.theme = "dark";The payoff
You get dynamic, user-controllable theming with essentially zero runtime cost, no hydration flash, and palettes that stay perceptually consistent. The browser was always the best theming runtime — Tailwind v4 and oklch just let us use it properly.