oklch colors

[check: …]
📖 Pair this live Tailwind demo with the companion guide (.md) — this page is the rendered ground truth. ↗ Builds on frontend/tailwind: design tokens (the @theme namespace — OKLCH is the color value format v4 tokens live in).

sRGB/HSL → OKLCH: why v4 switched

CSS historically used sRGB (#hex, rgb()) and HSL (hsl()). Both are non-perceptual: a "50% lightness" yellow in HSL is glaringly brighter than a "50% lightness" blue — equal numbers, unequal eyes. OKLCH is built on the OKLab model, which is calibrated to human vision. Equal lightness steps look equal across every hue. Tailwind v4 made OKLCH the default so its -50…-950 ramps are visually consistent for every color.

axisrangewhat it controls
L — Lightness01 (or 0%100%)perceived brightness. 0=black, 1=white. Calibrated to vision, not to physics.
C — Chroma0~0.4colorfulness / saturation. 0=gray, higher=vivid. Capped by the display gamut — very high C may clip on sRGB screens.
H — Hue0360the color wheel angle (0=red, 90=yellow-green, 180=cyan, 270=blue, 360=red again).
/ alpha01 (or %)opacity. Tailwind emits color-mix(in oklab, … / alpha) for /40 modifiers.
oklch(0.7 0.15 250) /* L=70% C=0.15 H=250 (blue) */ oklch(0.7 0.15 25) /* same L & C, hue=25 (red) — SAME perceived brightness */ oklch(0.7 0.15 250 / 0.5) /* 50% opacity */

1 · OKLCH explorer — drag L / C / H, watch the swatch + live rgb conversion

Each slider drives one axis. The big swatch is set to oklch(L C H) directly. The browser resolves it and we read back getComputedStyle to show the live sRGB conversion — proof the browser parses and applies OKLCH.

0.70
0.150
250°

oklch(0.7 0.15 250)

→ rgb(—)

Click blue 250 then red 25: same L and C, only H changes. Notice the swatch brightness stays constant — that's perceptual uniformity. In HSL the same hue-swap would make red look brighter than blue.

2 · perceptual uniformity — same lightness, every hue

Both ramps below sweep the full hue wheel in equal steps with FIXED lightness & saturation. The OKLCH ramp should look evenly bright across every hue. The HSL ramp (same fixed lightness & saturation) looks uneven: yellow glows, blue stays muddy. Equal numbers, unequal eyes — the flaw OKLCH fixes.

OKLCH — L=0.70, C=0.13, H sweeps 0°→360°

HSL — L=70%, S=75%, H sweeps 0°→360°

3 · same hue, different color spaces — sRGB vs OKLCH gradient

Gradients between two colors of very different hue pass through a "gray dead zone" in sRGB (the midpoint desaturates). OKLCH interpolation keeps chroma up, so the transition stays vivid. The CSS engine uses in oklch as the interpolation color space.

sRGB (default) — red → blue, muddy midpoint

OKLCH interpolation — red → blue, vivid throughout

/* Tailwind v4 gradient utilities emit oklch interpolation by default */ .bg-linear-to-r { background-image: linear-gradient(to right, var(--tw-gradient-stops)); } /* --tw-gradient-stops use the --color-* oklch values */

4 · Tailwind v4 default palette — every -500 is OKLCH

These are the actual --color-*-500 theme values shipped in Tailwind v4 (verified against tailwindcss.com/docs/colors, v4.3). The swatch is painted straight from the OKLCH value.

tokenOKLCH valueswatchcomputed rgb (live)
red-500oklch(0.637 0.237 25.331)
amber-500oklch(0.769 0.188 70.080)
green-500oklch(0.723 0.219 149.579)
cyan-500oklch(0.715 0.143 215.221)
blue-500oklch(0.623 0.214 259.815)
violet-500oklch(0.606 0.250 292.717)
fuchsia-500oklch(0.667 0.295 322.150)
pink-500oklch(0.656 0.241 354.308)
gray-500oklch(0.551 0.027 264.364)

Note the hue axis: red≈25, amber≈70, green≈150, cyan≈215, blue≈260, violet≈293, pink≈354. The lightness of green-500 (0.723) and cyan-500 (0.715) is nearly identical — so they look equally bright side-by-side, by design.

5 · gold-check — @theme OKLCH token applied & resolved

The swatch below uses class="bg-brand". The brand color is defined in the page's <style type="text/tailwindcss"> block as --color-brand: oklch(0.7 0.15 250). The gold-check reads getComputedStyle(swatch).backgroundColor and confirms the CDN compiled the OKLCH token into a real, opaque color — printing the live rgb conversion as proof.

intent → pattern

intentpatternwhy
define a custom OKLCH color @theme { --color-brand: oklch(0.7 0.15 250); } enables bg-brand, text-brand, border-brand — perceptually uniform
50% opacity tint bg-brand/50 v4 emits color-mix(in oklab, var(--color-brand) 50%, transparent) — perceptually correct alpha
override a default ramp --color-gray-500: oklch(0.551 0.027 264.364) redefine any --color-*-* token; utilities update automatically
vivid gradient (no dead zone) bg-linear-to-r from-red-500 to-blue-500 v4 stops are OKLCH values → interpolation stays saturated
disable a color family --color-lime-*: initial; removes the --color-lime-* vars from output entirely
arbitrary OKLCH value bg-[oklch(0.7_0.15_250)] one-off color without touching @theme (underscores = spaces)