web · 7 min read

Hex, RGB, HSL and OKLCH — how colour is written

Four ways to write the same colours. Three of them describe what a screen does; one describes what you see, and the difference shows up the moment you build a palette.

Every colour notation on the web ends up as three numbers driving red, green and blue subpixels. The notations differ in what those numbers mean to a person reading them, and that turns out to matter a great deal.

Hex and RGB are the same thing

#48750d is three pairs of hexadecimal digits — red 72, green 117, blue 13. rgb(72 117 13) is the same numbers in decimal. There is no difference in capability; hex is shorter and RGB is readable.

Both describe the hardware. Neither tells you anything useful about the colour: nothing in #48750d suggests it is a dark olive green, and adjusting it means guessing at three numbers that interact.

HSL made it readable

Hue as an angle, saturation and lightness as percentages. Suddenly you can reason about a colour — same hue, less saturation — and generate a palette by holding the hue and stepping the lightness.

That was the promise, and it half worked. The problem is that HSL is computed from RGB with straightforward arithmetic that knows nothing about human vision.

Where HSL lightness lies

Your eye is far more sensitive to green than to blue. Pure yellow at hsl(60 100% 50%) and pure blue at hsl(240 100% 50%) have the same stated lightness, and one of them is nearly white while the other is nearly black.

The consequences show up everywhere once you look. A palette built by stepping HSL lightness has uneven steps — crowded at one end, spread at the other. A hue rotation at fixed lightness swings visibly brighter and darker as it goes round. And a colour that passes a contrast check at one hue fails at another with identical HSL numbers, because contrast is computed from real luminance and HSL is not.

What OKLCH fixes

Same three ideas — lightness, chroma, hue — built on a perceptual model rather than on RGB arithmetic.

L is perceived lightness, so two colours at the same L genuinely look equally light. C is chroma, how colourful, and unlike saturation it is not a percentage of anything — it has an absolute scale, and how much of it is available depends on the lightness and the hue. H is the hue angle.

Which makes the things people actually do straightforward. A palette of even steps is even. A hue rotation holds its brightness. Two brand colours can be matched for lightness by making one number equal.

The chroma point has a real consequence worth knowing: dark greens cannot be very chromatic. Not a limitation of the notation but of the colour space — and it is exactly why a green dark enough to pass a contrast requirement against white reads as olive-grey whatever you do to it.

Colours outside sRGB

OKLCH can describe colours no ordinary screen can show, and modern displays can show more than sRGB covers.

So a conversion from OKLCH to hex may have nothing exact to return, and clips to the nearest colour in range. Converting hex to OKLCH is always exact, because hex cannot describe anything OKLCH cannot.

Practically: author in OKLCH, and treat the hex your converter gives you as the sRGB rendering rather than as the same colour.

Which to use

Hex for a value you are copying between tools. Compact, universal, and fine as an output format.

RGB when you need alpha and readability together, though every notation supports alpha now.

HSL if it is already in the codebase and working. It is not wrong; it just misleads you about lightness.

OKLCH for anything you are designing rather than transcribing: palettes, colour scales, tokens, dark mode variants. Browser support is broad enough to author in directly.

The short version

Hex and RGB describe hardware. HSL is readable and its lightness does not match your eye. OKLCH does, which is why palettes built in it come out even and palettes built in HSL need hand-correcting.

And whichever you author in, check the result against a contrast requirement rather than assuming a lightness number implies one.

Questions

What is OKLCH and why use it?

A colour notation with three parts: lightness, chroma and hue. Its lightness figure matches what your eye actually perceives, which HSL’s does not — so two colours with the same OKLCH lightness look equally light, and two with the same HSL lightness frequently do not.

How do I convert OKLCH to hex?

Any colour converter does it, and the conversion is one-way in an important sense: OKLCH can describe colours outside the sRGB range that hex covers, so those get clipped to the nearest representable one. Going hex to OKLCH is always exact.

What is the difference between OKLCH and HSL?

Both give you lightness, saturation and hue in a readable form. HSL computes them from RGB with simple arithmetic that ignores human vision, so its lightness treats yellow and blue as equal when they are nothing of the sort. OKLCH is built on a perceptual model, so its numbers behave the way you expect them to.

Why does lightening my HSL colour look wrong?

Because HSL lightness is not perceived lightness. Adding 10% to a yellow and to a blue produces two very different visual jumps, which is why palettes generated by stepping HSL lightness look uneven — light steps crowd together and dark ones spread apart.

Is hex still fine to use?

Yes. Hex, RGB and OKLCH can all describe the same sRGB colours, and hex is compact and universally understood. It is a poor format to think in — nothing about #48750d suggests how light or saturated it is — so people increasingly author in OKLCH and ship whatever the build produces.

Tools from this guide