Why does Accessible Palette say "Stop using HSL for color systems!"

Updated

HSL is a convenient way to describe a color, but it is a poor coordinate system for building a color system. Accessible Palette's warning comes down to one property: HSL is not perceptually uniform. When you move the L slider in HSL, the color does not get uniformly lighter or darker to the human eye, and its hue and saturation appear to shift along the way. That makes it hard to produce a ramp of colors with predictable, evenly spaced contrast — exactly what you need for accessible UI palettes. Accessible Palette instead builds ramps in CIELAB and LCh, which are designed to be perceptually uniform.

The core problem: HSL's "L" is not perceived lightness

In HSL, L is a mathematical lightness value, not a measure of how light a color looks. Two colors with the same HSL L can look very different in brightness, and a single hue at a fixed saturation will not step evenly through perceived lightness as you change L.

A few consequences:

  • Hue drift. As you raise or lower L in HSL, the apparent hue of a color can shift. A "blue" ramp built in HSL may look purple at the dark end and cyan at the light end.
  • Saturation distortion. Perceived saturation changes as L changes, so a ramp that looks evenly saturated in HSL terms does not look evenly saturated to the eye.
  • Uneven contrast steps. Because perceived lightness does not track HSL L linearly, the WCAG contrast ratios between adjacent steps come out irregular — some steps jump, others barely move.

Why this breaks accessible color systems

An accessible color system usually needs a ramp like 50, 100, 200 … 900 where each step is a predictable distance from the next, and where specific steps hit specific contrast targets against a background. If your underlying space is not perceptually uniform, you end up hand-tuning every swatch: nudging hue, correcting saturation, and re-checking contrast ratios one by one. The system stops being systematic.

Accessible Palette's own example ramps show what a uniform space buys you. In its red ramp, the steps are labeled with both CIELAB lightness and WCAG contrast:

Step CIELAB L WCAG 2 contrast Hex
50 98.2 1 #FFF8F8
100 93.95 1.1 #FFE9E6
200 85.1 1.4 #FFC8C0
300 76.5 1.8 #FEA69A
400 67.65 2.4 #FE7F6E
500 57.8 3.4 #E26352
600 47.6 4.8 #B85143
700 40.4 6.3 #9C4438
800 32.4 8.5 #7D372D
900 23.55 11.7 #5D2822

Notice how lightness decreases in even perceptual increments while contrast rises in a controlled way — the 600 step lands at roughly 4.8, near the WCAG AA threshold for normal text. That kind of alignment is what you are trying to engineer, and it is much easier when the color space itself behaves uniformly.

What CIELAB and LCh give you instead

CIELAB (and its cylindrical form, LCh) are based on how humans actually perceive color differences:

  • L* is perceptual lightness, so equal steps in L* look like equal steps in brightness.
  • C* is chroma (colorfulness) and h is hue angle, so you can hold hue steady while adjusting lightness and chroma independently.
  • Because the space is perceptually uniform, a ramp built along L* produces more even contrast spacing, making WCAG targets easier to hit deliberately rather than by trial and error.

Accessible Palette exposes exactly these controls — L, C, and H in CIELAB/LCh — and reports the WCAG 2 and WCAG 3 contrast values for each generated step, so you can see the contrast consequences of your lightness choices as you build.

Practical takeaway

  • Use HSL for quick one-off colors or CSS tweaks where perceptual accuracy does not matter.
  • Do not use HSL to generate a multi-step palette if you care about consistent perceived lightness or predictable WCAG contrast.
  • Build ramps in a perceptually uniform space (CIELAB/LCh) so lightness steps are even and contrast targets are reachable by design.
  • Verify the result against WCAG contrast ratios at the specific steps you will use for text and UI elements.