Getting started
Theming
Three layers of plain CSS custom properties. No ThemeProvider is required for any of it.
The three layers
| Layer | Example | Changes with theme? |
|---|---|---|
| 1 · Primitive | --noksha-violet-500 | No — 11 generated steps per hue |
| 2 · Semantic | --noksha-bg-surface | Yes — the only layer that does |
| 3 · Component | --noksha-button-h-md | No — derived from the scales |
Components only ever read layers 2 and 3, never layer 1. That is what makes a new theme a forty-line CSS block instead of a fork.
Rebranding
One declaration and the whole library follows — every tone, every hover state, both modes.
:root {
--noksha-brand: #0EA5E9;
}Why OKLCH, not HSL
In HSL, hsl(60 100% 50%) and hsl(240 100% 50%) claim the same lightness but differ by roughly ten times in perceived brightness — so an HSL scale needs hand-tuning for every hue. In OKLCH, L is perceived lightness, so one lightness ramp works for every hue and the scales below were generated, not designed.
#6D4AFF#E5484D#30A46C#F1A10DEvery step is gamut-mapped into sRGB by lowering chroma only, so the lightness ramp holds exactly regardless of hue. Foregrounds are picked per step by relative luminance, and the build fails if a pairing cannot reach 4.5:1.
Dark mode
Dark applies through three selectors so a class-based setup, an attribute-based one, and plain system preference all work without configuration.
/* Light is the :root default. */
:root { --noksha-bg-canvas: oklch(0.995 0.003 283.66); }
/* Dark applies through three selectors, so every setup works. */
.dark,
[data-theme='dark'] { --noksha-bg-canvas: oklch(0.18 0.008 283.66); }
@media (prefers-color-scheme: dark) {
:root:not(.light):not([data-theme='light']) { … }
}Add themeScript() to your <head> to avoid a flash on first paint — see Installation.
Retuning the scales
| Scale | Variable | Effect |
|---|---|---|
| Radius | --noksha-radius-base | 0 is sharp, 0.5rem is soft. |
| Density | --noksha-density | 0.875 compact · 1 default · 1.125 comfortable. Drives every control height and pad. |
| Motion | --noksha-duration-* | All animation reads these; prefers-reduced-motion zeroes them globally. |
| Typography | --noksha-text-* | Fluid clamp() steps, so headings scale with the viewport. |
More than one brand
Multi-tenant apps scope a second palette to a selector and swap one attribute at runtime.
import { emitThemeOverride } from '@noksha-ui/tokens';
// A second brand costs one scoped block, not a fork.
emitThemeOverride('[data-brand="acme"]', { brand: '#F97316' });