Short answer
Define each step by lightness in a space where lightness tracks luminance — CIELAB L*, or OKLCH as a close proxy — rather than by HSL lightness or by eye. Because WCAG contrast depends only on luminance, and L* is a direct function of luminance, a step defined by L* has the same contrast against white and black in every hue. That lets you write rules such as 'any two steps 50 L* apart pass 4.5:1' that hold across the whole palette.
A design system's scale — the familiar 50 to 900 steps of each hue — exists to be combined: text on backgrounds, borders on surfaces, icons on buttons. Every such combination has a WCAG contrast requirement, so the most useful property a scale can have is that its step numbers predict contrast. HSL does not give that: 'lightness 50%' is a bright yellow with about 1:1 contrast against white and a deep blue with over 8:1. Scales tuned by eye are better but inconsistent, so a team ends up checking every pair by hand. A scale defined by perceptual lightness makes the step number itself the contrast guarantee, which is what lets automated tooling and busy designers pick pairs safely.
WCAG computes contrast from relative luminance Y, and CIELAB L* is a fixed function of Y alone — hue and chroma do not enter it. So the contrast between two colours is determined entirely by their two L* values, whatever their hues. The first table computes, for each gap in L*, the lowest contrast any pair with that gap can have (the worst case sits at the dark end of the range). A gap of 40 guarantees just over 3:1, enough for large text, borders and icons; a gap of 50 lands a hair under 4.5:1, so body-text pairs need slightly more — anything over about 50.1, and 51 gives a small margin. Material's HCT colour space builds on the same property: its 'tone' is L*, and Google's tooling uses tone differences to reason about contrast between tonal-palette steps.
The rule needs every colour to be in gamut at its target L*. A very saturated yellow cannot exist at L* 40 in sRGB; chroma has to fall as the step gets darker.
The second table shows one way to place ten steps. The light end is crowded — 97, 92, 84 — because pale steps are used as surfaces and subtle fills, where small differences matter and there is a lot of luminance change packed into little L*. The middle steps around L* 54 to 44 are where hue is most vivid and where the 'brand' step usually sits; note that L* 54 already passes 4.5:1 against black but only reaches 3:1-plus against white, which is why so many primary buttons with white labels fail. Steps at or below about L* 44 carry white text; steps at or above about 54 carry black. Keeping the same L* targets for every hue means 'blue-600' and 'red-600' are interchangeable in any pairing rule, even though their chroma differs.
Lightness fixes contrast; chroma and hue decide character. A scale built at constant chroma runs out of gamut at the ends — very light and very dark colours cannot be saturated in sRGB — so chroma is usually highest in the middle and tapers towards both ends. Many hand-tuned scales also rotate hue slightly across the steps (yellows warming towards orange as they darken, blues shifting towards violet) to avoid muddy dark yellows and chalky light blues. OKLCH is a convenient authoring space because its lightness is close to perceptual and its chroma is independent of lightness, but its L is not identical to CIELAB L*; if a scale must guarantee WCAG contrast, verify the final sRGB values rather than trusting the OKLCH numbers alone.
| L* gap between two steps | Lowest possible contrast | Safe use |
|---|---|---|
| 20 | 1.59:1 | Decoration only |
| 30 | 2.24:1 | Decoration only |
| 40 | 3.16:1 | Large text, borders, icons |
| 45 | 3.75:1 | Large text, borders, icons |
| 50 | 4.48:1 | Large text, borders, icons |
| 55 | 5.37:1 | Body text pair |
| 60 | 6.46:1 | Body text pair |
| 70 | 9.14:1 | Body text pair |
| Step | L* | Contrast with white | Contrast with black | Carries body text |
|---|---|---|---|---|
| 50 | 97 | 1.07:1 | 19.48:1 | text on black |
| 100 | 92 | 1.22:1 | 17.14:1 | text on black |
| 200 | 84 | 1.52:1 | 13.81:1 | text on black |
| 300 | 74 | 2.03:1 | 10.34:1 | text on black |
| 400 | 64 | 2.77:1 | 7.56:1 | text on black |
| 500 | 54 | 3.89:1 | 5.39:1 | text on black |
| 600 | 44 | 5.57:1 | 3.76:1 | text on white |
| 700 | 35 | 7.77:1 | 2.69:1 | text on white |
| 800 | 26 | 10.77:1 | 1.94:1 | text on white |
| 900 | 17 | 14.37:1 | 1.46:1 | text on white |
Why: Steps were defined in HSL or by eye, so equal step numbers have unequal lightness.
Fix: Re-anchor each step to a common L* target across all hues.
Why: The brand step sits around L* 50–55, just short of the 4.5:1 point.
Fix: Use a step at L* 44 or darker for filled buttons, or use dark text.
Why: Chroma and hue were held constant as lightness fell.
Fix: Taper chroma and allow a small hue rotation at the dark end.
Each statement is labelled by kind — established fact, a standard’s requirement, observed market data, a convention, or Colourwise’s own interpretation or analysis — with the strength of the evidence behind it.
Colourwise analysisStrong evidence
Because CIELAB L* is a function of relative luminance only, two colours' WCAG contrast ratio is fixed by their L* values; any pair 40 L* apart has at least about 3.1:1, and a gap of just over 50 is needed to guarantee 4.5:1.
Based on: Computed by Colourwise from the CIELAB L* definition and the WCAG 2.2 contrast formula, searching all pairs with each gap; see the table.
Source: CIE (International Commission on Illumination) publications; Web Content Accessibility Guidelines (WCAG) 2.2
FactModerate evidence
Google's HCT colour space, used for Material colour schemes, combines hue and chroma from CAM16 with tone taken from CIELAB L*, and builds tonal palettes that vary only in tone.
Source: Material Color Utilities (HCT colour space, tonal palettes, dynamic colour)
FactModerate evidence
OKLCH lightness approximates perceived lightness but is not the same quantity as CIELAB L*, so equal OKLCH L does not guarantee equal WCAG contrast.
Source: A perceptual color space for image processing (Oklab); CSS Color Module Level 4
Reviewed 29 September 2026. Colourwise summarises its sources in its own words and does not reproduce standards text or proprietary colour data. Spotted an error? Tell us.