Reference

Color Theory for Developers — HEX, RGB, HSL, OKLCH

What each colour space actually represents, why developers historically picked the wrong ones, and what to use in 2026.

ByMayank RaiUpdated May 4, 2026

Most developers I've worked with have a working knowledge of HEX and RGB, a vague intuition for HSL, and treat anything beyond that as designer territory. This is fine until you need to do something the simple spaces are bad at: generate a palette of colours that look equally bright, animate between two colours without passing through muddy greys, or pick an accessible text colour algorithmically. At that point you need the perceptually-uniform colour spaces, and they are worth understanding.

This article explains each major colour space, what it represents, what it's good and bad at, and which one to use when. It assumes no design background — just an interest in why your "simple" colour-mixing code keeps producing ugly results.

RGB and HEX

RGB describes a colour as three numbers: how much red, green, and blue light to emit. Each channel is typically 0–255 (8 bits per channel, 24 bits total). HEX is just RGB written in base 16: #FF8800 is red 255, green 136, blue 0.

RGB is how monitors actually work, which is why every other colour space ultimately has to convert to RGB before display. But it is a terrible space for human reasoning. The number 128 in the green channel is not "half as bright" as 255 — it is much darker than that, because human perception of brightness is nonlinear. Two RGB triples that look equally bright to a computer (e.g. (0, 255, 0) and (255, 0, 0)) look wildly different to a human (the green looks much brighter). Mixing two RGB colours by averaging their channels usually produces an ugly intermediate.

HSL and HSV

HSL splits a colour into Hue (the angle on the colour wheel), Saturation (how vivid), and Lightness (how light or dark). It is computed directly from RGB by a few simple operations.

HSL is much more intuitive than RGB for picking colours. To get a slightly darker version of a blue, decrease L. To get a less vivid version, decrease S. To get a different shade with the same intensity, change H. Most colour pickers in design tools default to HSL or HSV (a close relative).

But HSL has a hidden flaw: it is not perceptually uniform. Two colours with the same L value can look very different in brightness. A pure yellow at hsl(60, 100%, 50%) looks much brighter than a pure blue at hsl(240, 100%, 50%). If you generate a palette by varying H at fixed L and S, your palette will have visibly uneven brightness — bright yellows and greens, dim blues and purples. This is why algorithmically-generated HSL palettes always look slightly wrong.

LCH and Lab

Lab (officially CIELAB) was developed in the 1970s to model human colour perception. It splits a colour into Lightness (perceptually uniform — equal L means equal apparent brightness regardless of hue), and two opponent-colour axes (a: green-red, b: blue-yellow). LCH is the same space expressed in cylindrical coordinates: Lightness, Chroma (saturation), Hue. Same idea as HSL but in a perceptually uniform space.

LCH is what designers reach for when they want a palette that looks even. Generate twelve hues at fixed L and C in LCH, and they will all look the same brightness. The same trick in HSL gives you the uneven-brightness mess described earlier.

LCH has its own problems though. The hue lines aren't perfectly straight — interpolating between two LCH colours can produce a curve that drifts through unintended hues. And not every LCH triple is achievable on a typical sRGB monitor; you can specify colours that are out of gamut and need to be clipped, with various ways the clipping can fail.

OKLCH and OKLab

OKLab was published in 2020 by Björn Ottosson as an improvement on Lab. It is also perceptually uniform but has straighter hue lines, better behaviour for interpolation, and is computed from a more accurate model of human colour perception. OKLCH is the cylindrical form, the same way LCH is to Lab.

OKLCH is the right default for new design systems in 2026. CSS Color Level 4 added native support (oklch(0.7 0.15 30)), every major design tool now exposes it, and Tailwind 4 uses OKLCH internally for its palette. The properties that matter:

  • Equal L means equal perceived brightness across all hues.
  • Linear interpolation between two colours produces a smooth gradient that doesn't drift through ugly intermediate hues.
  • Hue rotation produces colour wheels that look balanced.
  • Lightening and darkening (changing L) preserves the apparent hue.

Practical recipes

Generating a palette

Pick a base colour in OKLCH. To get a palette of N hues all at equal brightness, hold L and C constant and vary H by 360/N degrees. To get a tints-and-shades scale (50, 100, 200, … 900 like Tailwind), hold H constant and vary L from 0.95 down to 0.15.

Mixing two colours

Convert to OKLab, average each channel, convert back. CSS does this natively with color-mix(in oklch, blue 50%, red). Doing the same in RGB will produce a muddy intermediate that passes through dark grey.

Picking a contrasting text colour

Compute the OKLab L of the background. If L > 0.6, use dark text (black or near-black). If L < 0.6, use light text (white or near-white). This gives you visually correct contrast without the WCAG luminance hack that produces incorrect results for some hues.

Animating between colours

CSS now supports specifying the interpolation colour space: transition: background-color 200ms linear; background-color: oklch(0.7 0.15 30); with color-interpolation: oklch;. The animation passes through visually pleasing intermediates instead of the dull greys you get by default.

Gamut and clipping

Display gamut is the set of colours a screen can actually show. sRGB (the gamut every monitor supports) is smaller than what OKLCH can specify. Display P3 (modern Apple devices) is ~25% larger but still smaller than the full OKLCH space.

When you specify an OKLCH colour outside the display gamut, the browser clips it to the nearest in-gamut colour. The clipping algorithm matters: naïve channel clipping can shift the hue noticeably; chroma reduction (lower the C until the colour fits) preserves hue at the cost of vividness. CSS now supports specifying the gamut explicitly: oklch(0.7 0.3 30 / 1) is OKLCH, oklch(0.7 0.3 30 / 1) display-p3 targets the P3 gamut.

For most web work, target sRGB and accept that very vivid OKLCH colours will be slightly desaturated on standard displays. For media work or apps targeting Apple devices, P3 is worth using.

When to still use older spaces

HEX and RGB are still the right choice for serialising fixed brand colours where the exact bits matter — your logo's blue should be exactly #1E3A8Ain every place it's used, not subject to OKLCH rounding through colour-space conversions.

HSL is fine for one-off colour pickers and quick adjustments where perceptual uniformity doesn't matter. Most designers still think in HSL because it's what colour pickers default to.

For everything that involves generating colours algorithmically — design tokens, theme generation, data visualisation, gradients, animations — OKLCH is the right tool.

Tools

Toolkiya's colour converter handles round-trips between HEX, RGB, HSL, LCH, and OKLCH. The colour pickershows the same colour in every space at once, which is useful for understanding why a HEX value that looks "medium dark" has an L of 0.62 in OKLCH.

For palette generation, both tools chain together: pick a base colour, convert to OKLCH, generate a series with varying L and C, and convert back to HEX for use in a stylesheet. The result is a palette that looks visually balanced — which an HSL-generated palette will not.

Closing thought

The history of colour on the web has been a slow march from "colours we can encode in a few bytes" (HEX, RGB) toward "colours that match how humans actually see" (OKLCH). Each step has been a small improvement to a specific shortcoming of the previous space. OKLCH is not the final word — colour science continues to evolve — but it is the right default for 2026, and the small effort to learn it pays off every time you have to generate, mix, or animate colours in code.

MR

Built & maintained by Mayank Rai

Solo developer based in Lucknow, India · Last updated May 4, 2026

Convert colours between formats

No signup, no upload to servers. Your files stay private.

Try Free on Toolkiya