Perceptually uniform lightness
Scales are generated in HSLuv, whose L is CIE L* — so step 600 is the same perceived lightness at every hue, and a contrast assignment written for blue holds for yellow.
Perceptual color / restrictions
API v4
Give bezold one color. It returns a twelve-step scale whose lightness is perceptually uniform across every hue — and, for any background you pick, the exact set of values that clear WCAG and APCA on it.
Generator
The background list is restricted to surfaces that can actually carry text — a scale of twelve has ten of them, not twelve. Everything the grid shows is a floor: a step marked legal clears its rule at every hue on the wheel, in every variant, so an assignment you make here still holds when you change the hue.
The same faces decisionsby.design offers, plus its neutral. Each one’s vibrancy sweep is derived here rather than copied.
Each color is measured and anchored onto the board, exact hex included. Colors close enough in hue are captured into one palette — the family’s hue shift carries the scale through every one of them.
Using only your colors
Every family is anchored by your values — or the neutral stands at the single open one, which adds no color.
Expanding your palette
Your values kept as given; open families get derived hues. The neutral never counts as expansion.
Every palette on the board carries its own sweep and level. These controls edit the one named here; the others keep whatever they were given.
Hue
Total sweep across the scale, zero at the derived origin. Rotation never touches lightness, so every bracket above holds at any sweep.
The step where rotation is zero — the scale pivots around it.
Linear rotates tints and shades in opposite directions; each-way rotates both the same way onto one hue. Curved swaps the straight profile for x³/x² — flat near the origin, steep at the ends.
Saturation
One axis, not three treatments. The named variants are positions on it, and at a variant’s own anchor the output is byte-identical to that variant.
The saturation analogue of the sweep: the level holds at the origin and walks toward the extremes, every step still running the full pipeline at its own level.
The step where the saturation shift is zero.
Print-safe takes a palette made to meet WCAG and APCA contrast standards and fits it to what a printing press can reproduce, based on the ISO 12647-2:2013 standard for offset printing on coated paper (PSO Coated v3). Each step’s saturation is adjusted to values where it can be printed on paper at that step’s hue and lightness, keeping each color within a reasonable range of its screen original.
Off by default: without it the first palette fills the neutral roles and surfaces are drawn from it. A reading that needs a neutral — a typed grey captured into a palette, or the neutral standing at a scheme position — turns it on regardless.
Pick every surface you will actually ship. The darkest light one and the lightest dark one are what bind — an ink legal on those is legal on the rest.
White is offered; pure black is not.
2 backgrounds · brand 50 binds — the darkest of them, so an ink legal here is legal on the rest · 800–1000 carries body text
2 backgrounds · brand 900 binds — the lightest of them, so an ink legal here is legal on the rest · 0–100 carries body text
Every bracket is a floor, not a measurement. A step inside one clears its rule at every hue on the wheel, in every variant, so an assignment made here holds when you change the hue — which is the whole reason the table can be published once and cached forever. Ringed swatches are the backgrounds you chose.
light · brand 50 · binding of 2
Every color on this card was chosen by rule. The heading, the body, the edge of the field and the fill on the button each clear both standards against every background you picked.
This line is the non-body role — the quietest text these pages will legally carry.
dark · brand 900 · binding of 2
Every color on this card was chosen by rule. The heading, the body, the edge of the field and the fill on the button each clear both standards against every background you picked.
This line is the non-body role — the quietest text these pages will legally carry.
/* Generated by bezold — https://bezold.dev
* hue ~260, saturation 88, sweep derived
*
* The scale is perceptually uniform: a step is the same lightness at every
* hue, so the assignments below hold if you change the hue.
*/
:root {
/* Brand */
--brand-20: #f6faff;
--brand-50: #ecf3ff;
--brand-100: #dee9ff;
--brand-200: #b9cefd;
--brand-300: #91b0f8;
--brand-400: #5f88eb;
--brand-500: #4b6dd7;
--brand-600: #4055bf;
--brand-700: #353a97;
--brand-800: #2b266e;
--brand-900: #1c1248;
--brand-950: #140833;
/* Light mode */
/* background: brand white */
/* background: brand 50 */
--page: #ecf3ff;
--bodyLc90: #2b266e;
--bodyLc75: #353a97;
--nonBody: #353a97;
--heading: #353a97;
--uiBackground: #5f88eb;
--fill: #4055bf;
}
[data-theme="dark"] {
/* background: brand 900 */
/* background: brand 950 */
--page: #1c1248;
--bodyLc90: #dee9ff;
--bodyLc75: #dee9ff;
--nonBody: #dee9ff;
--heading: #dee9ff;
--uiBackground: #91b0f8;
--fill: #b9cefd;
}What it is
Scales are generated in HSLuv, whose L is CIE L* — so step 600 is the same perceived lightness at every hue, and a contrast assignment written for blue holds for yellow.
WCAG 2.2 is the requirement, and every legal ink clears it. APCA is advisory, and each legal ink carries its APCA figure: APCA decides most rows in dark mode, as WCAG decides some light-mode UI-background rows.
Every legal set is the worst figure achieved across the whole wheel, in every variant. Relying on a better per-palette number is how a system-wide rule becomes a per-palette accident.
The engine never returns one color where several would have passed. Choosing among a legal set is placement, and placement belongs to whatever is consuming it.
The same input always produces byte-identical output. No randomness, no timestamps, nothing that makes a snapshot test a coin flip.
Measured at build: rotating hue anywhere on the wheel moves contrast by at most 2.2 Lc. That is what lets one table describe every palette.
Dogfood
Its six deep bars sit at HSLuv L 54.0–54.1. Its six light bars sit at L 67.0–67.1. One row, one lightness, six hues — the perceptual-uniformity claim drawn rather than argued. Run detectVariant over any of them and it reads vibrant.
neutral · hue 263.2
brand · hue 263.2
Which is why the controls above are chips and toggles rather than sliders. The engine never reaches a browser — the only version of the restriction that restricts anything — so each change is a request. The design compensates by returning everything for a palette in one response: the scale, both pages’ legal sets, the usage map and the CSS. Nothing calls back mid-decision.
The legal-step table is hue-independent, so it is fetched once and cached against the engine version indefinitely. A palette is twelve hexes on the wire.
The finding
Lc 90 pages — dark ink
Gated Lc 75 page — dark ink
No body text, in either direction
Gated Lc 75 pages — light ink
Lc 90 pages — light ink