# Why OKLCH?

Every hue looks evenly matched at each stop, so you can swap families without rechecking contrast.

The live page shows twelve hues at HSL lightness 50% next to the same hues at stop `_350`, with a switch that removes the hue. The HSL colors run from L\* 32 (blue) to L\* 97 (yellow); every palette color sits at L\* 72.0. [Try it on the live page](https://colortokenskit.com/under-the-hood/why-oklch/).

These are twelve hues at HSL lightness 50%, and the same hues at our `_350`. Switch to "Lightness only" to take the hue away. The HSL row turns into grays from dark to nearly white, because HSL ignores how much brighter green looks than blue. Ours turns into one gray.

That's why we built the palette in [OKLCH](https://colortokenskit.com/reference/glossary/index.md#oklab-and-oklch): every hue can share one lightness at each stop, so `_600` is 4.68:1 on white in every hue, and you can swap families without checking contrast again. [How did we choose and build these colors?](https://colortokenskit.com/basics/how-the-colors-were-built/index.md) has the short version.

## How OKLCH describes a color

OKLCH gives a color a lightness (L), a [chroma](https://colortokenskit.com/reference/glossary/index.md#chroma) (C, how far it is from gray) and a [hue](https://colortokenskit.com/reference/glossary/index.md#hue) (H, an angle around the wheel). Björn Ottosson designed it in 2020 so that equal steps in the numbers look like equal steps in color. CSS writes the same numbers as `oklch()`.

## Why hue and chroma come from OKLCH

```swift
Color.proBlue._50.toLCH().h    // 258°
Color.proBlue._600.toLCH().h   // 272°, like every darker stop
```

- proBlue.\_50: #eff6ff
- proBlue.\_300: #8fc0fe
- proBlue.\_600: #4176b8

Every `proBlue` stop sits at OKLCH hue 250°, and they read as one blue from light to dark. Measured in CIELab LCH, the same stops turn 14°. So a blue built at one LCH hue drifts toward purple as it gets lighter, while OKLCH holds it steady, and we take hue and chroma from OKLCH.

## Why lightness comes from CIELab L\*

```swift
let coral = OKLCHColor(l: 0.57, c: 0.08, h: 40)   // #A06753, L* 49.2, 4.61:1 on white
let teal = OKLCHColor(l: 0.57, c: 0.08, h: 190)   // #338682, L* 51.2, 4.29:1 on white
```

- coral: #a06753
- teal: #338682

They share an OKLCH lightness and chroma, but only the coral passes WCAG AA for text on white. WCAG contrast depends only on luminance, and OKLCH's L only comes close to it. CIELab's [L\*](https://colortokenskit.com/reference/glossary/index.md#cielab-and-l) tracks luminance exactly, so we hold L\* equal at each stop and use OKLCH for chroma and hue.

## Trade-offs and limits

Equal lightness costs some color: a yellow as dark as a blue reads as olive. [What does equal lightness cost?](https://colortokenskit.com/under-the-hood/trade-offs/index.md) covers each cost, and [fitting colors into Display P3](https://colortokenskit.com/under-the-hood/gamut-mapping/index.md) covers colors that screens can't show.

## Sources

- Björn Ottosson, [A perceptual color space for image processing](https://bottosson.github.io/posts/oklab/) (2020), which introduces OKLab.
- W3C, [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/), which defines `oklch()` and HSL in CSS.
- W3C, [Web Content Accessibility Guidelines (WCAG) 2.2](https://www.w3.org/TR/WCAG22/), which defines relative luminance and contrast ratio.
- The [ColorTokensKit source](https://github.com/metasidd/ColorTokensKit-Swift), for how the palette uses L\* and OKLCH.

## See also

- [.contrastRatio(to:method:)](https://colortokenskit.com/api/contrast-ratio/index.md): Check WCAG 2 and APCA contrast in Swift, and keep your color pairs passing with a unit test.
- [How gradients blend and ease](https://colortokenskit.com/under-the-hood/gradient-blends-and-easing/index.md): Why plain gradients turn gray, how each blend travels between colors, and where easing puts them.
- [Replacing your app colors](https://colortokenskit.com/getting-started/replacing-colors/index.md): Move backgrounds, text, outlines, states, gradients and charts to tokens, one screen at a time.
- [.toOKLCH()](https://colortokenskit.com/api/to-oklch/index.md): Read a color's lightness, chroma and hue the way the palette sees them, to compare or adjust it.

---

From ColorTokensKit, by Penguin Design Ventures: https://colortokenskit.com/under-the-hood/why-oklch/ (updated September 26, 2026).
