Articles /

New featurecolor

OKLCH and modern color

Perceptually uniform colors and light-dark() for design systems that just work.

What's wrong with hex and RGB?

Hex and RGB work in the sRGB color space, which is not perceptually uniform. When you lighten a color by 20 in lightness, the perceived change is different depending on the hue. Blues look much brighter after a small lightness bump; yellows barely change. This makes building consistent palettes (e.g. 9-step scales) hard by eye.

OKLCH is a perceptually uniform color space. Equal steps in L (lightness) look equal to the human eye, regardless of hue. That makes it far better for building design tokens, color scales, and accessible color pairs.

The OKLCH syntax

/* oklch(Lightness Chroma Hue / Alpha) */

oklch(0.5 0.2 260)          /* mid-light blue */
oklch(0.75 0.15 145)        /* mid-light green */
oklch(0.9 0.08 75)          /* light amber */
oklch(0.5 0.2 260 / 0.5)   /* 50% opacity */
oklch(1 0 0 / 0.15)         /* white at 15% — for glass overlays */

/* Ranges:
   L: 0 (black) → 1 (white)
   C: 0 (gray) → ~0.4 (most saturated, varies by hue)
   H: 0–360 (hue angle, same as HSL) */

Hue 0/360 is red, 120 is green, 240 is blue — familiar from HSL. Chroma is roughly "how colorful" — 0 is gray, 0.2–0.3 is vivid, and the maximum varies by hue (e.g. yellows max out around 0.3, some blues go to 0.4).

A scale built at the same L and C, varying only hue:

Building a design token scale

:root {
  /* Primary palette — same L steps, same hue (260 = blue) */
  --blue-100: oklch(0.96 0.04 265);
  --blue-200: oklch(0.88 0.08 265);
  --blue-300: oklch(0.76 0.13 265);
  --blue-400: oklch(0.63 0.18 265);
  --blue-500: oklch(0.52 0.22 265);  /* base accent */
  --blue-600: oklch(0.43 0.22 265);
  --blue-700: oklch(0.35 0.2 265);
  --blue-800: oklch(0.26 0.16 265);
  --blue-900: oklch(0.18 0.1 265);

  /* Semantic tokens */
  --color-primary:      var(--blue-500);
  --color-primary-soft: var(--blue-100);
  --color-primary-text: var(--blue-800);
}

The equal L steps (0.96, 0.88, 0.76, 0.63, 0.52...) produce visually even steps in the scale — much harder to achieve in hex.

light-dark() — one line dark mode

light-dark(light-value, dark-value) is a function that returns the first value in light mode and the second in dark mode — based on color-scheme.

:root { color-scheme: light dark; }

.button {
  /* One line instead of two @media blocks */
  background: light-dark(oklch(0.52 0.22 265), oklch(0.68 0.19 265));
  color: light-dark(white, oklch(0.14 0.02 260));
}

/* Works with any CSS color value — not just oklch */
.card {
  border: 1px solid light-dark(oklch(0 0 0 / 0.08), oklch(1 0 0 / 0.1));
}

Browser support: Chrome 123+, Safari 17.5+, Firefox 120+. Widely usable in 2026. Add a @media (prefers-color-scheme: dark) fallback for older browsers if needed.

Practical tools

  • oklch.com — interactive OKLCH color picker with conversion from hex.
  • evilmartians.com/oklch — deep background on why OKLCH.
  • Figma Variables — supports oklch values in 2026; define your scale once and reference it.
Start here: take your current brand color, convert it to OKLCH, note the H value, then build your scale by keeping H and C fixed and varying L in equal steps.