Addaly is in open beta. Things will change, and AI answers can be wrong — check anything that matters.

Design, Before Any Software

Hierarchy, type, colour and spacing — the part of design you can do with a pencil, before any software.

Lesson 36 of 749 min

The lightness slider is not lightness

Two colours, same number, nothing alike

Take hsl(60, 100%, 50%) and hsl(240, 100%, 50%). Both claim 50% lightness. The first is a blazing yellow, the first thing you see on any page. The second is a deep blue you could set white text on.

Measure them properly and the gap is enormous. Relative luminance runs from 0 for black to 1 for white, and pure yellow comes out at about 0.93 while pure blue is about 0.07 — more than a twelvefold difference between two colours the model says are equally light.

This is not a bug in HSL so much as a statement about what HSL was for. It is a convenient reparameterisation of RGB that makes colour pickers easy to build, and its L is a geometric position inside the RGB cube, not a measurement of how bright something looks.

Why blue is dark and yellow is bright

The eye is not equally sensitive to all wavelengths. Sensitivity peaks in the green region, around 555 nanometres in daylight vision, and falls away steeply towards both ends of the spectrum. That is why the luminance formula weights the channels so unevenly:

L = 0.2126·R + 0.7152·G + 0.0722·B

Green contributes nearly three-quarters of perceived brightness and blue about seven per cent. Yellow, which is red plus green at full strength, inherits almost all of it. Blue has almost none to inherit.

Everything downstream follows from this. It is why a yellow warning banner needs black text and a blue one needs white. It is why a naive greyscale conversion that averages the channels is wrong, as covered in the first block. And it is why a five-swatch palette that varies hue at constant HSL lightness collapses into an unusable mush of near-identical greys.

The model that does not lie: OKLCH

A perceptually uniform colour space is one where equal numeric steps correspond to roughly equal perceived differences. OKLCH is the one now available directly in CSS, and it has three components:

  • L — perceptual lightness, 0 to 1. A colour at L 0.5 looks mid-grey whatever its hue.
  • C — chroma, how colourful. 0 is grey; the practical maximum varies by hue.
  • H — hue angle, 0 to 360.
css
:root {
  --brand-500: oklch(0.55 0.18 255);
  --brand-300: oklch(0.75 0.12 255);
  --brand-700: oklch(0.38 0.16 255);
}

This buys two things that are genuinely difficult otherwise.

Pure hues, all at HSL 50 per cent lightnessYellow0.93Cyan0.79Green0.72Magenta0.28Red0.21Blue0.07relative luminance, 0 is black and 1 is whiteYellow carries more than twelve times the light of blue while the model reports both as equally light.That is why a yellow banner wants black text and a blue one white.
Pure hues, all at HSL 50 per cent lightnessYellow0.93Cyan0.79Green0.72Magenta0.28Red0.21Blue0.07relative luminance, 0 is black and 1 is whiteYellow carries more than twelve times the light ofblue while the model reports both as equally light.That is why a yellow banner wants black text and ablue one white.

A ramp with even steps. Move L by 0.08 each time and every step looks like the same size of change. In HSL the steps at the pale end are visually tiny and the steps at the dark end are enormous, which is why hand-built HSL ramps always need fiddling.

Categorical colours that match in lightness. For a chart, you want six colours that differ in hue and are the same brightness, so no series looks more important than another. Hold L and C constant, vary H, and you have it. In HSL this is impossible without measuring every swatch.

Browser support for oklch() arrived across all the major engines in 2023, and Chrome, Firefox and Safari developer tools all show OKLCH values in their colour pickers now.

The honest limitation

OKLCH can describe colours that a screen cannot show. Ask for oklch(0.7 0.35 145) and you are requesting a green more saturated than sRGB contains. The browser then maps it into gamut, and different browsers have historically clipped differently — which means the colour you get may not be the colour you specified, and it may not match between devices.

Practical guardrails: keep chroma modest, around 0.1 to 0.2 for interface colours, which stays comfortably inside sRGB for most hues. Check anything vivid on a real screen. And if you need certainty, specify a fallback:

css
.button { background: #2f6df6; background: oklch(0.55 0.18 255); }

The second declaration wins where it is understood and is ignored where it is not.

What to take away

If you build palettes in HSL — and most tools still do — you are not obliged to abandon them. What you must do is stop trusting the L number and start measuring. Every browser's developer tools will give you a contrast ratio, which is a luminance measurement in disguise, and the greyscale check from the first block gives you the same information visually.

The rule that survives whichever model you use: a palette is a set of lightness decisions with hues attached, not a set of hues with lightness attached. Build the ramp first, in a space that measures lightness honestly, and choose the hue afterwards.

The one thing to keep

HSL lightness is a position in the RGB cube rather than a measurement of brightness, so yellow and blue at the same L differ more than tenfold in luminance — build ramps in a perceptually uniform space such as OKLCH, or measure every swatch.

Before you move on

A designer builds a six-colour chart palette in HSL, holding lightness at 55% and varying hue evenly around the wheel. In greyscale the yellow series nearly disappears and the blue one looks almost black. Why?

Pick the one you would defend. Nobody sees your answer.

No ads. No data sale. No public scores on people. Ever.

© 2026 Addaly