zudo-css

Type to search...

to open search from anywhere

OKLCH Color Space

CreatedMar 13, 2026UpdatedMar 26, 2026Takeshi Takatsudo

The Problem

AI agents almost always generate colors in hex, rgb(), or hsl() format. These older color spaces have a fundamental flaw: they are not perceptually uniform. In HSL, two colors with the same lightness value (e.g., hsl(60, 100%, 50%) yellow and hsl(240, 100%, 50%) blue) appear drastically different in perceived brightness. This makes it nearly impossible to create consistent, accessible color palettes by simply adjusting hue values. AI-generated palettes in HSL often have inconsistent contrast ratios, muddy mid-tones, and colors that “jump” in perceived brightness across the spectrum.

The Solution

OKLCH (oklch()) is a CSS color function based on the Oklab perceptual color model. It uses three components:

  • L — Lightness (0% = black, 100% = white), perceptually linear
  • C — Chroma (0 = gray, higher = more vivid), represents colorfulness
  • H — Hue (0–360 degrees), the color angle on the color wheel

The key advantage: if you keep L constant and change H, the perceived brightness stays the same. This makes palette creation predictable — you can generate a set of colors that look equally bright to the human eye.

Why OKLCH Beats HSL

/* HSL: These "look" like the same lightness, but they're not */
.yellow {
  color: hsl(60, 100%, 50%); /* Appears very bright */
}

.blue {
  color: hsl(240, 100%, 50%); /* Appears much darker */
}

/* OKLCH: Same lightness = same perceived brightness */
.yellow {
  color: oklch(80% 0.18 90); /* Visually bright */
}

.blue {
  color: oklch(80% 0.18 264); /* Equally bright */
}

Code Examples

Basic OKLCH Syntax

:root {
  /* oklch(lightness chroma hue) */
  --brand-primary: oklch(55% 0.25 264); /* Vivid blue */
  --brand-secondary: oklch(65% 0.2 150); /* Teal-green */
  --brand-accent: oklch(70% 0.22 30); /* Warm orange */

  /* With alpha transparency */
  --overlay: oklch(20% 0 0 / 0.5); /* Semi-transparent black */
}

Creating a Perceptually Uniform Palette

By fixing lightness and chroma and only rotating hue, every color has the same visual weight:

:root {
  /* Categorical palette — all colors appear equally prominent */
  --chart-1: oklch(65% 0.2 30); /* Red-orange */
  --chart-2: oklch(65% 0.2 90); /* Yellow */
  --chart-3: oklch(65% 0.2 150); /* Green */
  --chart-4: oklch(65% 0.2 210); /* Cyan */
  --chart-5: oklch(65% 0.2 270); /* Blue */
  --chart-6: oklch(65% 0.2 330); /* Magenta */
}
OKLCH Uniform Lightness vs HSL Inconsistent Lightness

Lightness Scale for a Single Hue

:root {
  --blue-hue: 264;
  --blue-chroma: 0.15;

  --blue-50: oklch(97% var(--blue-chroma) var(--blue-hue));
  --blue-100: oklch(93% var(--blue-chroma) var(--blue-hue));
  --blue-200: oklch(85% var(--blue-chroma) var(--blue-hue));
  --blue-300: oklch(75% var(--blue-chroma) var(--blue-hue));
  --blue-400: oklch(65% var(--blue-chroma) var(--blue-hue));
  --blue-500: oklch(55% var(--blue-chroma) var(--blue-hue));
  --blue-600: oklch(45% var(--blue-chroma) var(--blue-hue));
  --blue-700: oklch(37% var(--blue-chroma) var(--blue-hue));
  --blue-800: oklch(30% var(--blue-chroma) var(--blue-hue));
  --blue-900: oklch(22% var(--blue-chroma) var(--blue-hue));
}

Theming with OKLCH Custom Properties

:root {
  --hue: 264;
  --chroma: 0.2;

  --color-primary: oklch(55% var(--chroma) var(--hue));
  --color-primary-light: oklch(75% var(--chroma) var(--hue));
  --color-primary-dark: oklch(35% var(--chroma) var(--hue));
  --color-primary-subtle: oklch(95% 0.03 var(--hue));

  --color-surface: oklch(99% 0.005 var(--hue));
  --color-text: oklch(20% 0.02 var(--hue));
  --color-text-muted: oklch(45% 0.02 var(--hue));
}

/* Change the entire theme by adjusting one variable */
.theme-green {
  --hue: 150;
}

.theme-red {
  --hue: 25;
}

Accessible Color Pairs

With OKLCH, you can guarantee contrast by controlling the lightness delta:

:root {
  /* A lightness difference of ~45-50% in oklch roughly maps to WCAG AA 4.5:1 */
  --bg: oklch(97% 0.01 264);
  --text: oklch(25% 0.02 264);

  --btn-bg: oklch(50% 0.2 264);
  --btn-text: oklch(98% 0.01 264);
}

OKLCH vs HSL — Real Comparison

/* Creating "same lightness" grays in HSL — they're not truly equal */
.hsl-problem {
  --gray-warm: hsl(30, 10%, 50%);
  --gray-cool: hsl(210, 10%, 50%);
  /* These two grays have visibly different perceived brightness */
}

/* OKLCH grays are genuinely perceptually matched */
.oklch-solution {
  --gray-warm: oklch(55% 0.02 60);
  --gray-cool: oklch(55% 0.02 250);
  /* These two grays actually look equally bright */
}

Common AI Mistakes

  • Defaulting to hex or hsl() for all color values when oklch() would produce more consistent palettes
  • Assuming HSL lightness is perceptually uniform — hsl(60, 100%, 50%) and hsl(240, 100%, 50%) look vastly different in brightness despite identical lightness values
  • Using chroma values that exceed the gamut for certain hue/lightness combinations — the browser will clip them, but the result may differ from intent
  • Not taking advantage of OKLCH’s hue rotation for generating multi-color palettes — AI often hard-codes each color independently instead of rotating hue
  • Creating color scales by evenly spacing lightness values (10%, 20%, 30%…) without considering that very high chroma at extreme lightness is out of gamut
  • Using oklch(0% 0 0) and oklch(100% 0 0) for black and white when simpler black and white keywords suffice

When to Use

  • Design system color tokens: OKLCH makes it straightforward to generate consistent lightness scales across different hues
  • Data visualization palettes: Categorical colors at the same perceived brightness prevent one color from dominating visually
  • Accessible theming: Controlling the lightness delta between background and text ensures predictable contrast
  • Dynamic theming: Rotating the hue custom property shifts the entire palette while preserving visual harmony

When to stay with hex/rgb

  • When targeting older browsers that don’t support OKLCH (pre-2023) and a fallback is impractical
  • When interfacing with design tools or APIs that only accept hex or rgb values
  • Single-color declarations where perceptual uniformity is irrelevant

References

Revision History