# Color you can do math with

> Read and write colors in OKLCH, build an even color ramp from one hue, and get an agent to migrate a hex palette without breaking it.

Module: Color, depth and surfaces · Level: beginner · 8 min · https://school.agents.org.in/design/surface/oklch

Drag the **L** slider below. The color gets lighter, and it stays the same blue. Now drag **H**: the hue spins round the wheel, and the color stays exactly as light. That is the whole trick of this lesson.

*[Interactive demo: One color, three numbers. The ramp underneath is built from the same hue.]*

## Three numbers you can read

Most colors on the web are written as hex (The familiar #3b82f6 style of writing a color: amounts of red, green and blue in base 16. Easy for screens, meaningless to read: you can't tell how light #3b82f6 looks): `#3b82f6`. A screen understands that. A person doesn't. You can't look at `#3b82f6` and know whether it is light or dark, or what happens if you nudge it.

OKLCH (A way of writing a color as three numbers: L (how light it looks to a person), C (how vivid) and H (which hue, as an angle). Because the numbers follow what eyes see, you can do math with them) writes the same blue as `oklch(0.623 0.188 260)`, three numbers in a row:

- **L**, lightness (How bright a color looks to a person, from 0 (black) to 1 (white). In OKLCH it is measured, so two colors with the same L really look equally light): how bright it looks to a person, 0 (black) to 1 (white).
- **C**, chroma (How vivid a color is. 0 is gray; around 0.3 is as saturated as most screens go. The highest chroma you can use depends on the hue and the lightness): how vivid. 0 is gray; around 0.3 is the loudest most screens show.
- **H**, hue (Which color it is, as an angle on a wheel: about 25 is red, 90 yellow, 145 green, 260 blue, 330 pink): which color, as an angle. Roughly 25 red, 90 yellow, 145 green, 260 blue, 330 pink.

> **In plain words.** OKLCH is a way of writing colors where the numbers mean what they say. Two colors with the same L really look equally light, so you can build palettes, fix contrast and make dark mode with simple arithmetic instead of guesswork.

## Why not HSL?

You may have met HSL (An older color notation (hue, saturation, lightness). Its lightness is a guess: yellow and blue at the same HSL lightness look nothing alike, so ramps built in HSL come out uneven), which also has a "lightness". The problem is that it lies. Toggle between the two rows below: at "50% lightness" HSL's yellow glows and its blue sinks. OKLCH's L 0.7 is one level across every hue.

*[Interactive demo: The gray bars measure how light each swatch really looks.]*

A ramp spaced on HSL inherits that lie: some steps jump, others barely move, and the light tints drift toward violet. **Never generate a scale in HSL.**

## Building a ramp

A ramp (One hue in a row of steps from nearly white to nearly black, usually numbered 50 to 950. A palette is a few ramps, not a pile of colors) is one hue in eleven steps, named 50, 100, 200 … 900, 950. The recipe the skill teaches has four parts, and you can watch each one in the demo above.

  **Step: Anchor L and space it evenly.** Step 50 sits near L 0.97, step 950 near L 0.22, and the steps between are evenly spaced. Even spacing is the point: every neighbor looks equally far apart. Never run to pure 0 or 1, where the color stops reading as its hue.

  **Step: Shape chroma as a curve.** Give each step a share of the most chroma it can hold: about 30% at the ends, rising to about 85% around 400–500. Tints stay airy, shades stay rich.

  **Step: Clamp per step.** The ceiling moves with lightness, so a vivid blue necessarily loses chroma toward both ends. That is the geometry of color, not a bug.

  **Step: Keep the hue fixed.** One H for all eleven steps. If an inherited scale wanders more than about 10° end to end, rebuild it on its middle hue.

## Colors a screen can't show

Push **C** up in the first demo and a warning appears: "past sRGB". OKLCH can name colors no ordinary screen shows. The range a screen can show is its gamut (The set of colors a screen can actually show. OKLCH can name colors outside it; those get clamped to the nearest one the screen can display), and its edge moves with hue and lightness. At L 0.5, violet can go to C 0.29 while cyan tops out near 0.09.

So two hues never share one chroma number. A brand blue, a success green and a warning amber feel like one family when each sits at the **same L** and the **same share of its own ceiling**.

When a value goes past the edge, clamp it yourself: keep L and H, lower C until it fits. Left alone, the browser maps it in a way you didn't choose.

```css
.cta { background: oklch(0.66 0.2 330); }                       /* safe everywhere */
@media (color-gamut: p3) { .cta { background: oklch(0.66 0.32 330); } } /* extra punch on wide-gamut screens */
```

That second line is for Display P3 (A wider gamut than standard sRGB, found on most recent phones and Macs. Extra-vivid colors can be layered on for these screens with a media query), the wider gamut on recent phones and Macs. *(The punch goes in the P3 layer. Raising the base color's chroma breaks it on every ordinary screen.)*

## Migrating a hex palette

Moving an existing file to OKLCH is a value-for-value swap. Nothing else in the file should change: not the order, not the comments, not the gradient shapes. Watch an agent do it properly.

*[Interactive demo: The agent converts values, keeps keywords, and flags a contrast problem instead of quietly fixing it.]*

The rules it followed:

1. Hex first, then `rgb()`/`rgba()`, then `hsl()`.
2. Alpha goes behind a slash: `rgba(255,255,255,.12)` becomes `oklch(1 0 0 / 0.12)`.
3. Never convert keywords (`currentColor`, `transparent`, `inherit`) or anything a third-party library reads as hex.
4. Re-check contrast on the pairs that matter, and report every changed line, old beside new.

**Migrate a palette to OKLCH (paste into your agent):**

```text
Migrate the colors in src/styles/tokens.css to oklch().
- Value for value: hex first, then rgb()/rgba(), then hsl(). Alpha goes after a slash.
- Keep comments, order and indentation; don't touch keywords (currentColor, transparent) or hex a library parses.
- Clamp anything out of sRGB by lowering chroma, holding L and H.
- Re-run contrast on text pairs and list any that fail 4.5:1. Don't fix them silently.
- Report every changed line: old value, new value, one-clause reason.
```

**Check yourself:** A brand teal and a brand violet should feel equally vivid. What do you copy between them?

- [ ] The same C value, e.g. 0.2
- [x] The same L and the same share of each hue's own chroma ceiling
- [ ] The same hex brightness
- [ ] The same HSL saturation

*Why:* Cyan-ish hues have a far lower chroma ceiling than violet. Copying one C over-saturates the roomy hue or flattens the tight one; matching L and the share of each ceiling keeps them a family.

## In the skill

- https://school.agents.org.in/skill/surface/MOC-surface.md
- https://school.agents.org.in/skill/surface/color-scales-oklch.md
- https://school.agents.org.in/skill/surface/oklch-gamut-and-migration.md
