OKLCH describes color the way humans perceive it: L (lightness), C (chroma/saturation), H (hue). It's the native format of daisyUI 5 themes and the reason modern palettes stop fighting you.
Why it matters
In hex/HSL, two colors with "the same lightness" often aren't — which is why palettes drift into mud or vibration. In OKLCH, equal L means perceptually equal weight. Build a palette by holding L steady and varying H; everything stays balanced automatically.
The recipe
- Ground: base-100 at L 0.94–0.98 (light) or 0.15–0.20 (dark). Give it a whisper of chroma (0.005–0.02) — pure gray reads dead, slightly warm/cool gray reads intentional.
- Structure: base-200/base-300 same hue, L steps of 0.04–0.06. In dark themes, higher elevation = lighter surface.
- Ink: base-content shares the ground hue, L ≥ 0.9 (on dark) or ≤ 0.25 (on light).
- Accent: primary at C 0.12–0.20. Below 0.10 reads washed; above 0.22 vibrates uncomfortably except in playful registers.
- Content pairs: every
-contentmust clear WCAG AA against its parent. Test pairs, not colors in isolation.
Dark-mode specifics
- Include
color-scheme: dark— browser chrome depends on it. - Desaturate semantic colors slightly (C −0.02); full-chroma neon on dark grounds hurts.
- Shadows barely register on dark — borders do the separating.
- Never flip to pure black (#000); L 0.15–0.17 keeps depth perception working.
Contrast quick-reference
| Text | Ground | Minimum ratio |
|---|---|---|
| Body | any | 4.5:1 |
| Large (≥24px or 19px bold) | any | 3:1 |
| UI components/borders | adjacent | 3:1 |
Compute real ratios from OKLCH with any converter — but as a rule of thumb, ΔL ≥ 0.55 between text and ground clears AA comfortably.
Anti-patterns
- Five accents "for variety" — dilution, not richness.
- Chroma on structural surfaces (keep base-* near-neutral).
- Same accent for links AND errors AND branding — meaning needs separation.