Skip to main content
Features

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";

Component Docs

FAQ