Skip to content
Orpheus

Which colour format should I use in CSS?

Hex and RGB are the same sRGB channel values in different notation. HSL is easier to reason about but its lightness is not perceptual. OKLCH is perceptually uniform, so equal lightness values genuinely look equally bright.

Updated 2026-08-24

Hex and RGB are the same thing written differently

This trips people up more than it should, so it is worth stating plainly: there is no conversion happening between hex and RGB in any meaningful sense. They are the same three numbers in two notations.

A colour on a normal screen is three channel intensities — red, green and blue — each stored in eight bits, giving 256 levels from 0 to 255. RGB notation writes those in decimal. Hex notation writes each as two hexadecimal digits, base 16, so 255 becomes ff and 0 becomes 00. The colour written as rgb(255, 87, 51) and the colour written as ff5733 are byte-identical. Converting between them is a change of base and nothing more.

The three-digit shorthand exists because each hex digit can be doubled: f80 expands to ff8800, not to f08000. That means shorthand can only express colours whose channel bytes both have identical digits, which is 4,096 of the 16.7 million available. It is convenient for round numbers and useless for anything sampled from a design.

Alpha extends the notation to eight digits, where the last pair is opacity on the same 0 to 255 scale — so 80 is roughly 50 percent, not 80 percent, a genuinely common mistake. The four-digit shorthand doubles the same way.

The important caveat is that all of these numbers are meaningless without a colour space, and the assumed one is sRGB. The same triple sent to a wide-gamut display without a space declared will render differently, which is why CSS gained display-p3 and why a colour picked in a photo editor working in Adobe RGB does not match the hex you paste into a stylesheet.

HSL is easier to think in and lies about brightness

HSL rearranges the same sRGB space into three axes that map onto how people describe colour: hue as an angle on a wheel from 0 to 360, saturation as a percentage of colourfulness, and lightness as a percentage from black to white.

The advantages are real. Making something darker is one number rather than three. Rotating a hue to find a complement is arithmetic rather than guesswork. A designer can read hsl(210, 80%, 45%) and know roughly what it is, which nobody can do with a hex triple.

The problem is the lightness axis. In HSL, lightness 50 percent means the midpoint between black and white in the geometry of the colour cube, and that has almost nothing to do with perceived brightness. Fully saturated yellow at 50 percent lightness and fully saturated blue at 50 percent lightness are wildly different in apparent brightness — the yellow reads as nearly white and the blue as nearly black. Human vision is far more sensitive to green than to blue, and HSL does not model that at all.

This matters the moment you build a palette systematically. Generating a set of colours at matching lightness produces a row of swatches that look nothing like a matched set, with the yellows glaring and the blues sinking. It matters again for accessibility, because contrast ratio depends on relative luminance, and two colours with identical HSL lightness can sit on opposite sides of the 4.5 to 1 threshold that body text requires.

HSV, sometimes called HSB, has the same weakness with a different geometry. Its value axis is the maximum channel, so every fully saturated hue sits at 100 percent value regardless of how bright it looks.

What OKLCH actually fixes

OKLCH is the cylindrical form of the OKLab colour space, published by Björn Ottosson in 2020, and it is now in every major browser. Its three components look like HSL rearranged — lightness, chroma and hue — but the space underneath is built to be perceptually uniform.

Perceptual uniformity means that equal numerical steps correspond to roughly equal perceived differences. In OKLCH, two colours with the same lightness value genuinely look equally bright, whatever their hue. Yellow and blue at L of 0.6 read as a matched pair. That single property is what makes the space worth adopting.

The consequences are practical. A palette generated by holding lightness and hue and varying chroma produces a coherent family. A set of tints generated by stepping lightness evenly produces steps that look even, rather than bunching in the middle and stretching at the ends. Interpolating between two colours passes through sensible intermediates instead of through the grey mud that sRGB interpolation produces when it crosses the middle of the cube.

Chroma replaces saturation and behaves differently in one way worth knowing: it is unbounded rather than a percentage, and not every combination of lightness, chroma and hue exists inside a display gamut. A high chroma at extreme lightness describes a colour no screen can show, and browsers clamp it to the nearest displayable one. Values up to about 0.37 cover the sRGB gamut; going beyond reaches into display-p3 on hardware that supports it.

The remaining reason to keep hex around is inertia and tooling. Every design tool, every brand guideline and every developer reads hex fluently. A reasonable arrangement is to define a palette in OKLCH where the uniformity earns its keep, and to keep hex equivalents documented for the places that expect them.

Building a scale that steps evenly

A shade ramp — the same colour at nine or ten strengths — is the most common thing anybody generates, and it is where the choice of space is most visible.

The naive method mixes toward white and toward black in sRGB. It produces a ramp that looks wrong in a specific way: the light end goes chalky and desaturated while the dark end collapses quickly into near-black, and the visual gaps between adjacent steps are uneven even though the numbers are evenly spaced. Interpolating in linear-light sRGB fixes some of the collapse and still leaves the perceptual gaps uneven, because linear light models the physics of the display rather than the response of the eye.

Doing it in OKLab produces even gaps, because that is precisely what the space was constructed for. Stepping lightness in equal increments while easing chroma down toward both ends gives a ramp where no two neighbours are noticeably closer than any other pair. It is worth measuring rather than trusting: computing the perceptual distance between each consecutive pair and checking that they agree within a few percent turns a subjective judgement into a number.

One more detail matters for interface work. A ramp intended to carry text needs its contrast checked against the surfaces it will sit on, and perceptual lightness is not the same as the relative luminance that the contrast formula uses. A ramp that steps evenly by eye can still cross the 4.5 to 1 threshold in an awkward place, so the two properties have to be checked separately.

Questions

Is hex better than RGB?
Neither is better; they encode identical sRGB values in different bases. Hex is more compact and universal in tooling, RGB is easier to manipulate arithmetically, and both describe exactly the same colour.
What does the 80 mean in an eight-digit hex colour?
It is alpha on a 0 to 255 scale written in hexadecimal, so 80 is 128 out of 255 — roughly 50 percent opacity, not 80 percent. This is a common misreading.
Why do my HSL colours look inconsistent at the same lightness?
Because HSL lightness is a geometric midpoint rather than a perceptual one, and human vision is far more sensitive to green than to blue. Yellow and blue at 50 percent lightness differ enormously in apparent brightness.
Is OKLCH supported in browsers?
Yes, it has shipped in all major engines. Providing a hex fallback is still reasonable for older environments, and the two can coexist in the same declaration block.
Why does my chroma value get clamped?
Chroma in OKLCH is unbounded, so some combinations of lightness, chroma and hue fall outside what a display can produce. The browser maps those to the nearest colour inside the available gamut.