🚀AdvancedAdvanced Visual Design

CSS Color Spaces and Modern Color Functions

Use oklch, lch, lab, and color-mix() to create perceptually uniform color palettes, smooth gradients, and dynamic mixing.

Overview

Modern CSS supports color spaces beyond sRGB, allowing wider gamut colors (P3 display colors) and perceptually uniform manipulation. **Why oklch is important:** In HSL, equal lightness increments look visually unequal. In oklch, equal increments look equal to human eyes: ```css /* oklch(lightness chroma hue) */ .primary { color: oklch(0.55 0.2 264); } /* indigo */ .primary-2 { color: oklch(0.65 0.2 264); } /* lighter, same chroma/hue */ .primary-3 { color: oklch(0.45 0.2 264); } /* darker, same chroma/hue */ ``` **Color functions:** ```css /* Mix two colors */ color: color-mix(in oklch, #6366f1 60%, white); /* P3 wide-gamut color */ color: color(display-p3 0.2 0.5 1); /* Relative color syntax */ color: oklch(from var(--primary) calc(l + 0.1) c h); ``` **Color spaces supported:** - `srgb` traditional web color space - `display-p3` wide gamut (more vivid) - `oklch` perceptually uniform (best for palettes) - `oklab` oklch in Cartesian form - `lch` like oklch but older standard - `lab` like oklab but older standard - `hsl` familiar but non-uniform

Key Points

  • •oklch(L C H) where L is lightness 0-1, C is chroma 0-0.4, H is hue 0-360
  • •oklch is perceptually uniform: +0.1 lightness always looks the same visual step
  • •color-mix(in colorspace, color1 pct%, color2) mixes two colors in a given color space
  • •display-p3 has a wider gamut than sRGB, showing more vivid colors on P3 displays
  • •Relative color syntax: oklch(from var(--base) l c calc(h + 30)) rotates the hue
  • •color() function targets any color space: color(display-p3 r g b)
  • •Use @supports (color: color(display-p3 0 0 0)) to feature-detect wide gamut support
  • •Gradients in oklch avoid the gray muddy midpoint issue of HSL gradients

Examples

Building a palette with oklch

:root {
  /* One hue, varying lightness (perceptually even steps) */
  --indigo-100: oklch(0.93 0.05 264);
  --indigo-200: oklch(0.85 0.09 264);
  --indigo-300: oklch(0.75 0.13 264);
  --indigo-400: oklch(0.65 0.17 264);
  --indigo-500: oklch(0.55 0.20 264);  /* base */
  --indigo-600: oklch(0.45 0.20 264);
  --indigo-700: oklch(0.35 0.18 264);
  --indigo-800: oklch(0.27 0.14 264);
  --indigo-900: oklch(0.20 0.09 264);
}

Each step is exactly 0.1 lightness apart, so the palette looks visually even. In HSL you would need irregular increments to achieve the same result.

color-mix() for hover states and tints

:root {
  --brand: oklch(0.55 0.2 264);
}

.btn {
  background: var(--brand);
  color: white;
}

.btn:hover {
  /* Mix with black for a darker hover */
  background: color-mix(in oklch, var(--brand) 80%, black);
}

.badge {
  /* Tinted background: 20% brand + 80% white */
  background: color-mix(in oklch, var(--brand) 20%, white);
  color: var(--brand);
}

.overlay {
  /* Semi-transparent variant */
  background: color-mix(in srgb, var(--brand) 15%, transparent);
}

Wide gamut display-p3 with sRGB fallback

.hero-text {
  /* Fallback for older browsers (sRGB) */
  color: #5b21b6;

  /* Wide gamut purple: more vivid on P3 displays */
  @supports (color: color(display-p3 0 0 0)) {
    color: color(display-p3 0.28 0.08 0.80);
  }
}

/* Or use media query for display gamut */
@media (color-gamut: p3) {
  .hero-accent {
    background: color(display-p3 0.20 0.80 0.50);
  }
}

Relative color syntax for dynamic theming

:root {
  --primary: oklch(0.55 0.2 264);
}

.card {
  /* Lighter version: +0.3 lightness, less chroma */
  --card-bg: oklch(from var(--primary) calc(l + 0.38) calc(c * 0.3) h);
  background: var(--card-bg);
}

.btn-primary {
  background: var(--primary);
  /* Darker on hover by reducing lightness */
  --hover: oklch(from var(--primary) calc(l - 0.08) c h);
}

.btn-primary:hover {
  background: var(--hover);
}

Relative color syntax (oklch from) lets you derive variants from a single source color using math, making theming trivial.

Common Mistakes

  • Using very high chroma values in oklch (above 0.37) which can exceed sRGB gamut and clip on standard monitors
  • Forgetting that oklab is the Cartesian form of oklch so they share the same color model but different coordinate formats
  • Expecting color-mix to work in all browsers without a fallback (use @supports)
  • Generating gradients in HSL and getting a muddy midpoint; use oklch gradients instead

Note

Start your design system with a single oklch hue and vary only the lightness (0.2 to 0.95) and chroma (0.05 to 0.25) to generate a full palette. Every color will look visually balanced because oklch lightness is perceptually uniform.

Exercises

Write CSS to style the HTML scaffold. The preview updates live — when it looks right, mark as done.

Loading editor…

Completed: 0 / 3 exercises

Knowledge Check

Question 1 of 3

What makes oklch preferable to HSL for building color palettes?