OKLCH Studio · Guides
OKLCH vs HSL: an honest comparison with real numbers
Updated 2026-10-17
If you have written CSS for a while, you probably think in HSL. Hue picks the colour, saturation sets how vivid it is, and lightness makes it lighter or darker. OKLCH has the same three-part shape, so it is natural to ask whether switching is worth the trouble. The honest answer is that it depends on what you are building, and that for some work HSL is perfectly good.
This comparison is built from measurement rather than opinion. Every number in the tables below was computed by the colour code that runs this site, so you can check them against any converter.
The short answer
HSL is a rearrangement of RGB. It was designed for convenience on early graphics hardware, so its three channels are easy to compute from red, green and blue and easy to read, but they do not follow human perception. In particular, the L in HSL does not mean "how light does this look". It is the midpoint of the largest and smallest RGB channels, which is a simple arithmetic idea rather than a perceptual one.
OKLCH is a perceptual model. Its L is built to predict perceived lightness, its C is a measured amount of colour, and its H is an angle in a space where equal angles are meant to look like roughly equal hue changes. If you are producing a palette, a theme, or a set of accessible text colours, that difference decides how much manual fixing you do afterwards.
Worked example: same HSL lightness, very different brightness
Take the six fully saturated hues in HSL, each at saturation 100% and lightness 50%. In HSL these are "the same lightness". The table converts each to OKLCH with this site's colour code, so the L column shows how light each one actually is, and the last column shows the WCAG contrast ratio of that colour as text on white.
| HSL | Hex | OKLCH L | OKLCH C | Contrast on white |
|---|---|---|---|---|
| hsl(0 100% 50%) | #FF0000 | 0.628 | 0.258 | 3.99:1 |
| hsl(60 100% 50%) | #FFFF00 | 0.968 | 0.211 | 1.07:1 |
| hsl(120 100% 50%) | #00FF00 | 0.866 | 0.295 | 1.37:1 |
| hsl(180 100% 50%) | #00FFFF | 0.905 | 0.155 | 1.25:1 |
| hsl(240 100% 50%) | #0000FF | 0.452 | 0.313 | 8.59:1 |
| hsl(300 100% 50%) | #FF00FF | 0.702 | 0.322 | 3.13:1 |
Yellow at hsl(60 100% 50%) has an OKLCH lightness of 0.968. Blue at hsl(240 100% 50%) has 0.452. Both are "50% lightness" in HSL, yet one is close to white in perceived brightness and the other is dark. As text on white the yellow reaches only 1.07:1, which is barely visible, while the blue reaches 8.59:1. Across the six hues the OKLCH lightness runs from 0.452 to 0.968.
This is the core reason HSL is awkward for design systems. If you build a button palette by setting every hue to hsl(h 70% 50%), the results will look inconsistent: some hues will glow and others will sink. You compensate by hand, one hue at a time, and the compensation is different for every saturation and lightness you choose.
The same test in OKLCH
Now hold OKLCH lightness at 0.62 and chroma at 0.15, and rotate the hue. Here the colours have the same L by construction, so the question is whether they behave similarly. The table shows hex values and the WCAG contrast ratio on white.
| oklch() | Hex | Contrast on white |
|---|---|---|
| oklch(0.620 0.150 30.0) | #D15D4D | 3.90:1 |
| oklch(0.620 0.150 90.0) | #A98000 | 3.63:1 |
| oklch(0.620 0.150 150.0) | #2E9E52 | 3.42:1 |
| oklch(0.620 0.150 210.0) | #009CB7 | 3.26:1 |
| oklch(0.620 0.150 270.0) | #647EE0 | 3.74:1 |
| oklch(0.620 0.150 330.0) | #B761B1 | 3.91:1 |
The ratios on white now sit between 3.26:1 and 3.91:1, a much narrower band than the 1.07:1 to 8.59:1 range of the HSL set. They are not identical, and it would be misleading to claim they are. WCAG contrast is calculated from relative luminance, which weights green most and blue least, while OKLCH lightness is a different perceptual model. But the gap is small enough that fixed lightness is a useful design handle: if a colour at L 0.62 is close to a contrast threshold, so are its neighbours at other hues.
Table of differences
| Aspect | HSL | OKLCH |
|---|---|---|
| Basis | Rearranged sRGB channels | Perceptual model (Oklab) in polar form |
| Lightness | Midpoint of the largest and smallest RGB channel | Predicts perceived lightness |
| Same L across hues | Looks very different | Looks about the same |
| Colour amount | Saturation, a fraction of what sRGB allows at that lightness | Chroma, an absolute distance from grey |
| Gamut | Always inside sRGB | Can describe colours outside sRGB and Display P3 |
| Out-of-range values | Not possible | Possible, and mapped or clipped at display time |
| Hue stability when changing L | Hue is fixed in RGB terms, but perceived hue can still shift | Designed to stay steady |
| Browser support in CSS | Universal | Current evergreen browsers; add a fallback for older ones |
| Familiarity | Widely known, easy to read | Newer, numbers take some getting used to |
Two rows deserve a note. Saturation in HSL is relative: 100% saturation at lightness 50% is the most vivid sRGB colour for that hue, but 100% at lightness 90% is a pastel. OKLCH chroma is absolute, so a chroma of 0.15 means the same distance from grey wherever you are on the lightness scale. And because OKLCH can express colours beyond sRGB, a value can be unreachable on a particular display, which HSL never allows.
Where OKLCH helps most
- Tonal scales. Pick lightness steps once and reuse them for every hue, which makes a palette feel consistent without per-hue tweaking.
- Themes and dark mode. Lightness relationships carry over when you re-map a palette for a dark background, so the logic is easier to keep.
- Accessible text. You can fix a failing colour by moving only the lightness, and the hue and chroma, and therefore the brand look, stay intact. The guide to fixing colour contrast shows the process.
- Gradients and mixing. Interpolating in OKLCH avoids the muddy grey midpoint that mixing saturated complementary colours in sRGB can produce.
Tailwind CSS v4 made this choice for its default palette: the colours are defined as oklch() values in the theme. You can browse them on the Tailwind v4 colours page and compare them with the old hex values in the v3 to v4 mapping.
What you pay for the switch
OKLCH has real costs. The first is a learning curve: nobody can read an OKLCH triple and picture the colour as quickly as they can with a hex they have seen a thousand times. The second is gamut. A value that is out of range for the screen is changed by the browser, and different environments handle that differently, so the exact result is less predictable than a hex code. The third is tooling: parts of the ecosystem, such as older design software, email clients and some image libraries, still only speak hex and RGB.
The usual answer is to author in OKLCH where you control the pipeline and export hex where you do not. The HEX to OKLCH and OKLCH to HEX converters let you move between them, and the CSS guide shows how to ship a hex fallback alongside an oklch() value.
When HSL is still fine
HSL remains a good choice more often than the enthusiasm around OKLCH suggests. If your design is a handful of fixed colours that someone picked by eye in a design tool, there is no perceptual consistency problem to solve, and rewriting everything in a new notation buys little. A quick hover effect, such as lightening a button by a few percentage points, works well enough in HSL because small shifts hide the unevenness.
- Small, hand-tuned palettes that are already approved and checked.
- Code that must run in environments without
oklch()support and cannot carry a fallback. - Teams where everyone reads HSL fluently and the colours are not generated algorithmically.
- Quick prototypes and demos, where a familiar notation saves time.
Mixing both is also normal. You can keep existing HSL tokens untouched and use OKLCH only for the new, generated parts of the system, such as a tonal scale or a contrast fix.
FAQ
- Is OKLCH better than HSL?
- For anything that depends on consistent perceived lightness, such as palettes, themes and accessible text colours, OKLCH is better because its lightness tracks what people see. For a small set of hand-picked colours, or where you need broad compatibility, HSL is still fine.
- Why does yellow look so much lighter than blue at the same HSL lightness?
- HSL lightness is a simple function of the RGB channel values and ignores the fact that the eye is more sensitive to green and red than to blue. A pure yellow has high red and green, so it looks very bright, while a pure blue has only a blue channel, so it looks dark. OKLCH lightness accounts for this.
- Do CSS and browsers support both?
- Yes. hsl() works everywhere. oklch() works in current evergreen browsers, so for older ones you can supply a hex or hsl() fallback first and wrap the oklch() declaration in an @supports rule. The CSS guide on this site has an example.
- Should I convert my existing HSL tokens to OKLCH?
- Not automatically. Convert when you are generating scales or themes, or when you keep fixing contrast by hand. Approved, stable colours can stay as they are, and you can convert individual values with the converters on this site when you need the OKLCH numbers.
- Does equal OKLCH lightness mean equal WCAG contrast?
- Close, but not exactly. Colours with the same OKLCH lightness have similar but not identical WCAG contrast ratios, as the worked example shows. Treat OKLCH lightness as a good design handle and still measure the final pair.
Try it
Paste an HSL colour, get its hex, and check the OKLCH values side by side. Then open the picker to adjust lightness.