Zero-Runtime Theming in React
Theme your entire app with CSS custom properties — no JavaScript provider, no hydration cost, no flash of wrong theme.
@import "tailwindcss";
@import "@ninna-ui/core/theme/presets/default.css";
/* Dark mode — just add .dark to <html> */
.dark {
color-scheme: dark;
}Key features
No JavaScript theme provider — theming is pure CSS
5 built-in theme presets with oklch perceptual colors
Dark mode via .dark class on <html> — no flash, no script
Custom themes by overriding CSS custom properties
Zero hydration cost — theme works before React loads
98 CSS custom properties for granular control
The problem with JS theming
Most React UI libraries theme in JavaScript: a ThemeProvider holds a theme object, components read it through React context, and styles are computed at runtime. This costs you bundle size, a context re-render surface, and a hydration step that can flash the wrong theme.
Ninna UI takes a different approach: theming is entirely in CSS. You import a preset file, and all components read from CSS custom properties. No JavaScript, no provider, no hydration.
How it works
Each theme preset is a CSS file that defines custom properties on :root. Components reference these properties via Tailwind v4's theme system:
/* default.css theme preset */
:root {
--color-primary: oklch(0.55 0.25 280);
--color-primary-content: oklch(0.98 0.01 280);
--color-base-100: oklch(0.98 0.005 280);
--color-base-200: oklch(0.95 0.008 280);
--color-base-content: oklch(0.25 0.02 280);
}
.dark {
--color-primary: oklch(0.65 0.22 280);
--color-base-100: oklch(0.18 0.02 280);
--color-base-200: oklch(0.22 0.02 280);
--color-base-content: oklch(0.92 0.01 280);
}Switching presets
Change the CSS import to switch the entire theme. No JavaScript changes needed:
/* Instead of default.css, import a different preset */
@import "@ninna-ui/core/theme/presets/forest.css";