Vorinda
Color Theory··13 min read

What Is OKLCH? A Practical Guide to CSS's Best Color Format

OKLCH is CSS Color 4's perceptually uniform color space. Learn what it is, how it beats HSL, browser support, and how to use it today.

Hasan Afzal
oklchcsscolor formatscolor theory
On this page

If you have ever built a color ramp in HSL and watched the middle swatches turn muddy, you have already met the problem OKLCH solves. So what is OKLCH? It is the modern, perceptually uniform color format from CSS Color 4 — the one that finally makes "10% lighter" mean the same thing for yellow as it does for blue. This guide covers what OKLCH is, how it beats HSL, its browser support and fallbacks, and how to put it to work in real projects today.

What OKLCH stands for (L, C, H)

OKLCH is built from two pieces. OK comes from Oklab, a color space published by Björn Ottosson in 2020 that was tuned to match human vision. LCH is the cylindrical way of reading that space: Lightness, Chroma, and Hue. So OKLCH is simply Oklab expressed in the same intuitive angle-and-distance terms that make HSL easy to reason about.

  • L — Lightness. How light or dark the color looks, from 0 (black) to 1 (white). Crucially, this is perceived lightness, not a raw signal value.
  • C — Chroma. How colorful or saturated it is. 0 is a pure gray; higher numbers are more vivid.
  • H — Hue. The angle on the color wheel in degrees, 0360, exactly like the hue you already know from HSL.

The relationship mirrors something you already use every day: HSL is a human-friendly cylinder wrapped around RGB, and OKLCH is a human-friendly cylinder wrapped around Oklab. The difference is what sits underneath. RGB was built for screen hardware; Oklab was built for eyes. For a refresher on the older trio, see HEX vs RGB vs HSL.

The problem with HSL: lightness is a lie

HSL feels great until you trust its lightness channel. The "L" in HSL is a mathematical midpoint between the brightest and darkest RGB components — it has almost nothing to do with how bright a color looks. The result is that two colors with identical HSL lightness can be worlds apart in perceived brightness.

The classic example is yellow versus blue, both set to exactly 50% HSL lightness:

ColorHSLOKLCH lightnessRelative luminance
Yellowhsl(60 100% 50%)0.9680.928
Bluehsl(240 100% 50%)0.4520.072

Same "lightness" on paper, yet yellow is roughly 13 times more luminous than blue. OKLCH reports them honestly: yellow lands near the top of the lightness scale at 0.968, blue sits in the lower-middle at 0.452.

This is why HSL color scales look uneven. When you generate a 50950 ramp by stepping HSL lightness in equal increments, the steps that pass through yellows and greens jump in apparent brightness while the blues and purples barely move. The ramp looks lumpy in the middle and crowded at the ends. Designers end up hand-correcting every swatch, which defeats the point of a systematic scale.

What OKLCH fixes: perceptual uniformity + wide gamut (P3)

OKLCH addresses two separate limitations at once.

Perceptual uniformity

Because Oklab was fitted to perception, equal numeric steps produce equal visual steps. Move L from 0.6 to 0.7 and the color looks one notch lighter — whether it is red, green, or blue. That single property is what makes OKLCH the right tool for generating scales, tuning hover states, and shifting an entire palette into dark mode without surprises.

Here are three colors that share the same lightness and chroma and differ only in hue. In OKLCH they read as equally bright; try the same trick in HSL and they will not:

OKLCHsRGB HEXReads as
oklch(0.7 0.15 30)#ED7665warm coral
oklch(0.7 0.15 145)#5BB661green
oklch(0.7 0.15 250)#4BA3F7sky blue

Wide gamut (P3)

HSL, HEX, and rgb() are locked to the sRGB gamut — the color range of a typical 2010s monitor. Modern phones and laptops ship wider Display P3 screens that can show noticeably more saturated reds, greens, and cyans. OKLCH is not bounded by sRGB, so a value like oklch(0.7 0.25 145) can describe a green that is simply unreachable in #rrggbb. On a P3 display it renders fully; on an sRGB display the browser maps it back into range. You write one value and the hardware shows you the most vivid version it can.

OKLCH syntax & value ranges (with examples)

The CSS function is space-separated, with an optional alpha after a slash:

/* oklch(L C H) */
color: oklch(0.62 0.19 260);

/* with alpha */
color: oklch(0.62 0.19 260 / 0.5);

/* L and alpha also accept percentages */
color: oklch(62% 0.19 260 / 50%);

Note there are no commas — that is the modern CSS Color 4 grammar, the same style used by rgb(255 0 0) today. Here is what each slot accepts:

ComponentMeaningRangeNotes
LPerceived lightness01 or 0%100%0 = black, 1 = white
CChroma (colorfulness)0 → ~0.37 sRGB, ~0.4 P30 = gray; technically unbounded
HHue angle0360 (degrees)same wheel as HSL hue
AAlpha (optional)01 or 0%100%written after /

A few practical notes. Chroma has no hard maximum in the spec, but each lightness/hue pair has a real-world ceiling before the color falls outside what a display can show; push past it and the browser clips the color to the gamut edge. You can also use the none keyword for a missing component — oklch(0.5 0 none) is a neutral gray whose hue is intentionally undefined, which helps gradients and color-mix() behave predictably.

Some everyday values to anchor your intuition:

--white:  oklch(1 0 0);
--black:  oklch(0 0 0);
--gray:   oklch(0.6 0 0);          /* chroma 0 = neutral */
--brand:  oklch(0.62 0.19 260);    /* a confident blue, ≈ #3B82F6 */
--accent: oklch(0.66 0.21 354);    /* a hot pink, ≈ #EC4899 */

Where OKLCH shines (scales, gradients, theming/dark mode)

Building color scales

This is OKLCH's headline use case. Take a familiar blue ramp and read it in OKLCH and you can see the structure: lightness descends in fairly even steps while hue stays nearly constant.

StepHEXOKLCH
50#EFF6FFoklch(0.97 0.014 255)
100#DBEAFEoklch(0.93 0.032 256)
200#BFDBFEoklch(0.88 0.057 254)
300#93C5FDoklch(0.81 0.096 252)
400#60A5FAoklch(0.71 0.143 255)
500#3B82F6oklch(0.62 0.188 260)
600#2563EBoklch(0.55 0.215 263)
700#1D4ED8oklch(0.49 0.217 264)
800#1E40AFoklch(0.42 0.181 266)
900#1E3A8Aoklch(0.38 0.138 266)
950#172554oklch(0.28 0.087 268)

Because lightness is now a trustworthy dial, you can design the ramp by formula — pick eleven evenly spaced L values, hold the hue steady, and shape chroma so it peaks in the mid-tones and tapers at the extremes. Every step lands where your eye expects it. A Shade & Tint Generator does this spacing for you, and the Brand Color Kit turns one brand color into a full, consistent UI scale.

Smoother gradients

CSS lets you choose the interpolation space for a gradient. Blend two saturated colors in the default sRGB space and the midpoint often sags into a muddy gray; blend them in oklch and the gradient keeps its chroma the whole way across.

.banner {
  /* stays vivid through the middle */
  background: linear-gradient(in oklch, oklch(0.65 0.2 260), oklch(0.7 0.2 30));
}

Because OKLCH is a polar space, you can even control which way the hue travels around the wheel with shorter hue (default) or longer hue. The CSS Gradient Generator lets you preview these interpolation modes side by side.

Theming and dark mode

Dark mode is where perceptual lightness pays off most. To darken a theme you mostly want to flip lightness while keeping hue and a similar chroma, and OKLCH makes that a near-mechanical edit:

:root {
  --bg:    oklch(0.98 0.005 260);
  --fg:    oklch(0.25 0.03 260);
  --brand: oklch(0.62 0.19 260);
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg:    oklch(0.18 0.02 260);
    --fg:    oklch(0.92 0.02 260);
    --brand: oklch(0.72 0.17 260);  /* lighter L, slightly less chroma */
  }
}

No hue drift, no surprise brightness jumps. For the full playbook, see the dark mode color palette guide.

Browser support & sRGB fallbacks (@supports, color-mix)

Browser support

OKLCH is well past the experimental stage. The oklch() function shipped years ago and is now Baseline widely available:

Browseroklch() supported since
Safari15.4 (March 2022)
Chrome / Edge111 (March 2023)
Firefox113 (May 2023)

That puts real-world coverage comfortably above 90%. The companion functions you will reach for — color-mix() (Chrome 111, Safari 16.2, Firefox 113) — landed on a similar timeline.

Fallback patterns

For the small slice of legacy browsers, the simplest fallback uses the cascade. List a HEX value first; browsers that understand oklch() use the second line, and the rest harmlessly ignore it:

.btn {
  background: #3b82f6;              /* fallback */
  background: oklch(0.62 0.19 260); /* preferred */
}

When you need a fallback only around a block of OKLCH-dependent rules, gate it with feature queries:

.btn { background: #3b82f6; }

@supports (color: oklch(0 0 0)) {
  .btn { background: oklch(0.62 0.19 260); }
}

And color-mix() is the cleanest way to derive states like hover or disabled without hardcoding a second value — mixing in OKLCH keeps the result perceptually sensible:

:root { --brand: oklch(0.62 0.19 260); }

.btn:hover  { background: color-mix(in oklch, var(--brand), black 12%); }
.btn:active { background: color-mix(in oklch, var(--brand), black 20%); }

If you can target newer engines, relative color syntax is even tidier — it computes a new color from an existing one, so a hover state becomes "the brand color, ten points darker":

.btn:hover { background: oklch(from var(--brand) calc(l - 0.1) c h); }

Relative color syntax is newer than oklch() itself (Safari 16.4, Chrome 119, Firefox 128), so keep a fallback in front of it for now.

OKLCH in Tailwind v4 & design tokens

Tailwind CSS v4 ships its entire default palette authored in OKLCH, and reaches into the P3 gamut to make the brighter shades more saturated than their old sRGB hex equivalents could be. That is a strong signal about where the ecosystem is heading: the most popular utility framework now treats OKLCH as the native format for color, not a curiosity.

Defining your own tokens in a Tailwind v4 @theme block looks like this:

@theme {
  --color-brand-50:  oklch(0.97 0.014 260);
  --color-brand-500: oklch(0.62 0.19 260);
  --color-brand-900: oklch(0.38 0.14 266);
}

The same principle applies to plain CSS custom properties or any design-token pipeline (Style Dictionary, Figma variables, and so on). Storing tokens in OKLCH means a designer can adjust one channel — bump chroma for a punchier brand, drop lightness for a denser dark theme — and the whole system stays coherent. For a deeper Tailwind walkthrough, read the Tailwind color palette guide.

Converting between HEX/RGB and OKLCH

You will rarely author colors from scratch in OKLCH at first; more often you are translating an existing brand HEX into OKLCH so you can build a system around it. A few reliable options:

  • A converter tool. Paste any HEX, RGB, or HSL value into the Color Format Converter to read its OKLCH equivalent instantly, or grab a color from scratch with the Color Picker, which shows OKLCH alongside the other formats.
  • In JavaScript. The Culori library converts between OKLCH and every common space with sub-millisecond performance — ideal for build steps or runtime theming.
  • In CSS directly. Relative color syntax lets the browser do the conversion for you: oklch(from #3b82f6 l c h) reads the HEX, decomposes it into OKLCH, and lets you tweak any channel inline.

Going the other direction — OKLCH back to HEX — is just as easy, but remember that a wide-gamut OKLCH color may not have an exact sRGB HEX twin. When that happens, the converter returns the nearest in-gamut HEX, which is usually all you need for a fallback declaration.

A quick reference for the round trip:

HEXOKLCH
#3B82F6oklch(0.62 0.188 260)
#EF4444oklch(0.64 0.208 25)
#22C55Eoklch(0.72 0.192 150)
#FACC15oklch(0.86 0.173 92)
#000000oklch(0 0 0)

Frequently asked questions

What does OKLCH stand for?

OKLCH is the Lightness, Chroma, and Hue form of the Oklab color space — OK plus L, C, H. L is perceived lightness, C is chroma (colorfulness), and H is the hue angle in degrees. It is the cylindrical, human-friendly version of Oklab, much like HSL is a friendlier face on RGB.

Is OKLCH better than HSL?

For building color systems, yes. OKLCH is perceptually uniform, so equal lightness changes look equal across every hue, and it can reach wide-gamut P3 colors that HSL cannot describe. HSL is still fine for quick one-off tweaks, but its lightness channel does not match what your eye actually sees.

Do all browsers support OKLCH?

Effectively yes. The oklch() function has shipped in Chrome, Edge, Firefox, and Safari since 2023 and is now Baseline widely available, covering well over 90% of users. For very old browsers, add a HEX or RGB fallback declaration before the oklch() line.

How do I convert HEX to OKLCH?

Paste the HEX value into a converter such as Vorinda's Color Format Converter to read its OKLCH equivalent, or compute it in code with a library like Culori. In modern CSS you can also derive colors directly with relative color syntax: oklch(from #3b82f6 l c h).

What is a good OKLCH chroma value?

Chroma starts at 0 (gray) and rarely needs to exceed about 0.37 in sRGB or 0.4 in P3. UI neutrals sit around 0.01 to 0.04, comfortable brand colors land near 0.10 to 0.20, and 0.25 or higher reads as vivid or neon.