HEX, RGB, HSL and the Colours You Cannot Mix in Them
Why #808080 is not half as bright as white, and why HSL lightness lies about lightness.
Three notations describe the same colours, and picking the right one for the job makes the difference between adjusting a palette in seconds and fighting it.
The three
- HEX —
#2563EB. Two hex digits each for red, green and blue, 0–255. Compact, universal, and completely opaque to a human: nothing about those six characters suggests "blue". - RGB —
rgb(37 99 235). The same three numbers in decimal. Matches how a screen works — three light sources added together. - HSL —
hsl(221 83% 53%). Hue as an angle on the colour wheel, saturation and lightness as percentages. The only one of the three you can reason about: keep the hue, drop the lightness, get a darker version of the same colour.
They are interchangeable. HSL is a straightforward transformation of RGB, not a wider range of colour, so anything expressible in one is expressible in the others.
Additive, which is why mixing surprises people
Screens emit light. Red plus green is yellow, all three at full is white, all three at zero is black. Anyone whose intuition comes from paint — where mixing everything gives mud — finds this backwards, and it is the root of most "why did that go grey" confusion.
The gamma trap
This is the part that catches even experienced people. sRGB values are not linear in light. #808080 is numerically halfway between black and white and emits roughly 22% of white's light, not 50%. The encoding devotes more precision to dark tones, because human vision does.
Two practical consequences. Blending two colours by averaging their RGB values gives a result darker than it should be — the classic muddy gradient through a dull middle. And resizing an image by averaging pixels without converting to linear light first systematically darkens it. Correct blending converts to linear, mixes, converts back.
HSL lightness is not perceived lightness
A related trap. Yellow and blue at the same HSL lightness look nothing alike — hsl(60 100% 50%) is dazzling, hsl(240 100% 50%) is dark. HSL treats them as equally light because it is a geometric transformation of RGB, not a perceptual model.
This is why a palette built by holding HSL lightness constant across hues comes out uneven, and why CSS now offers oklch(): a perceptual space where equal lightness looks equal, and where interpolating between two colours passes through sensible intermediates instead of grey.
Contrast is computed, not eyeballed
WCAG contrast is a ratio between relative luminances, and luminance is computed from linearised channel values weighted 0.2126 red, 0.7152 green, 0.0722 blue. Green dominates because human vision is most sensitive to it.
The thresholds: 4.5:1 for normal text, 3:1 for large text. Worth checking rather than judging, because the gamma curve makes intuition unreliable — grey text that looks fine on white frequently fails.
Alpha and the hex forms
Both 4- and 8-digit hex are now widely supported: #RGBA and #RRGGBBAA. So #2563EB80 is that blue at 50% alpha. The 3-digit shorthand simply doubles each digit — #FFF is #FFFFFF — which means it can only express 4,096 of the 16.7 million colours.
Which to use
- HEX for fixed brand colours and anywhere terse matters.
- HSL for building a palette by hand — variations of one hue are trivial.
- OKLCH for anything where perceptual evenness matters: gradients, generated palettes, accessible colour ramps.
- RGB when you are computing with channels directly.