Glossary

Lab color

Lab color is a device-independent color space, formally CIELAB, that describes a color by lightness L and two opponent axes, a and b. The CIE created it in 1976 so that distances between colors roughly match how different they look to people. In CSS you write it with the lab() function from CSS Color Module Level 4, for example lab(54.62% 8.76 -65.79).

How it works

Lab has three coordinates.

  • L (lightness): 0 is black and 100 is diffuse white. CSS lets you write it as a number or a percentage, and 100% means 100, not 1. Values outside 0 to 100 are clamped when parsed.
  • a: the green to red axis. Negative values are green and positive values are a purplish red.
  • b: the blue to yellow axis. Negative values are blue or violet and positive values are yellow.

The a and b axes are signed and have no fixed limit. In practice they stay within about plus or minus 160 for real-world colors. In CSS, a percentage maps 100% to 125 on these axes. A color with a and b near 0 is a gray, and larger a and b mean more saturated colors.

The white point is D50, a daylight spectrum of about 5000 K. sRGB uses D65, so converting from sRGB takes a chromatic adaptation step, normally the linear Bradford transform, before the Lab formula runs. The formula uses a cube root above a threshold of 216/24389, with a linear segment below it.

const mul = (M, v) => M.map(r => r[0] * v[0] + r[1] * v[1] + r[2] * v[2]);
const toLinear = v => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
const SRGB_TO_XYZ = [[506752 / 1228815, 87881 / 245763, 12673 / 70218],
                     [87098 / 409605, 175762 / 245763, 12673 / 175545],
                     [7918 / 409605, 87881 / 737289, 1001167 / 1053270]];
const D65_TO_D50 = [[1.0479297925449969, 0.022946870601609652, -0.05019226628920524],
                    [0.02962780877005599, 0.9904344267538799, -0.017073799063418826],
                    [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371]];
const WHITE = [0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585];
const EPS = 216 / 24389, KAPPA = 24389 / 27;

function srgbToLab(r, g, b) {
  const lin = [r, g, b].map(v => toLinear(v / 255));
  const xyz = mul(D65_TO_D50, mul(SRGB_TO_XYZ, lin)).map((v, i) => v / WHITE[i]);
  const f = xyz.map(v => v > EPS ? Math.cbrt(v) : (KAPPA * v + 16) / 116);
  return [116 * f[1] - 16, 500 * (f[0] - f[1]), 200 * (f[1] - f[2])];
}
const show = a => a.map(v => v.toFixed(2).replace('-0.00', '0.00')).join(' ');
const blue = srgbToLab(59, 130, 246);
const dark = srgbToLab(37, 99, 235);
console.log('lab(' + show(srgbToLab(255, 255, 255)) + ')  white');
console.log('lab(' + show(srgbToLab(0, 0, 0)) + ')  black');
console.log('lab(' + show(srgbToLab(128, 128, 128)) + ')  gray 128');
console.log('lab(' + show(blue) + ')  #3b82f6');
console.log('lab(' + show(dark) + ')  #2563eb');
console.log('deltaE76 =', Math.hypot(...blue.map((v, i) => v - dark[i])).toFixed(2));
lab(100.00 0.00 0.00)  white
lab(0.00 0.00 0.00)  black
lab(53.59 0.00 0.00)  gray 128
lab(54.62 8.76 -65.79)  #3b82f6
lab(44.79 21.05 -75.60)  #2563eb
deltaE76 = 18.54

How do you measure the difference between two Lab colors?

The simplest Lab color difference is the straight-line distance between the two points, written Delta E and, in this form, known as CIE76. In the run above it is 18.54 for the two blues. Newer formulas such as Delta E 2000 correct for known weaknesses. The CSS Color 5 draft treats a Delta E 2000 of 1 or more as just visible and 5 or more as a different color.

Common pitfalls

  • Mixing white points: Lab in CSS is relative to D50, while sRGB is D65. Skipping the adaptation step shifts every result, most visibly in grays that should have a and b of 0.
  • Confusing Lab L with HSL L: pure blue and pure yellow share HSL lightness 50%, but their Lab L is 29.57 and 97.61. Lab L tracks how light the color looks.
  • Expecting identical decimals across tools: the CSS spec text lists sRGB blue as lab(29.567% 68.298 -112.0294), while the code above prints 29.57 68.29 -112.03. Rounding in matrices and white points moves the third significant digit, so compare with a tolerance.
  • Assuming every Lab value is displayable: many a and b combinations fall outside sRGB. They must be gamut mapped, and a color with L of 0 or 100 is shown as black or white.
  • Using Lab for smooth blue gradients: the blue region has known hue errors, where a blue that loses chroma drifts toward purple. The CSS spec points to Oklab as more uniform.

Related terms

  • LCH color — the polar form of the same space, with chroma and hue
  • RGB — the additive model Lab is converted from on screens
  • sRGB — the D65 screen space that must be adapted to D50
  • HSL — a simpler model whose lightness is not perceptual
  • CMYK — the print model that CSS resolves to Lab when a CMYK profile is supplied

See also