Practical Guides
Recipes: building palettes, checking contrast, and shipping OKLCH in CSS.
Everything before this chapter was understanding; this one is execution. Four recipes cover the work color systems actually demand: building a scale, auditing it, shipping it, and migrating an old one.
Recipe: build a 10-step scale#
A design-system scale (the blue-100 … blue-1000 family) is a walk through OKLCH space:
- 1 - Fix the hue. Pick your brand hue angle (say H = 250 for blue) and hold it constant. Optionally drift a few degrees toward the light end to counteract perceived hue shift.
- 2 - Ramp lightness evenly. Distribute L from ~0.98 down to ~0.24. Even steps in OKLCH L are even to the eye - this is the whole trick.
- 3 - Shape chroma as a bell.Chroma physically can't stay high near white or black; peak it in the middle (C ≈ 0.12–0.18) and taper both ends (C ≈ 0.02–0.06).
- 4 - Verify.The Lab's Lightness Progression should hug the ideal line; the Distance Matrix should show neighbouring ΔE ≥ ~7; Gamut Coverage should be clean for sRGB.
The Lab's generation methods automate exactly this: “Balanced” applies an eased lightness curve with Gaussian chroma; “Base Respecting” keeps your exact hue and chroma and ramps only lightness.
Recipe: audit a palette#
- 1 - Contrast every text pairing. Body text needs WCAG 4.5:1 (AA); check both white-on-color and color-on-white for each step. In the Lab, the Contrast Analysis card computes both plus APCA Lc.
- 2 - Find the flip point. Identify the step where text color must switch between dark and light (usually near L ≈ 0.65–0.7) and encode it as a rule, not a per-case choice.
- 3 - Run the CVD simulations. If two adjacent steps merge under deuteranopia, their roles (e.g. chart series) need more lightness separation.
- 4 - Grayscale sanity check.Toggle the Palettes card's grayscale preview: the ramp should still read as an ordered sequence.
Recipe: ship OKLCH in CSS#
All evergreen browsers parse oklch() (Chrome 111+, Safari 16.4+, Firefox 113+ - spring 2023). For anything older, ship a hex fallback the old-fashioned way - earlier declarations win when the newer syntax is unknown:
:root {
/* Fallback first, modern value second */
--brand: #3577c7;
--brand: oklch(0.58 0.14 250);
}
.button {
background: var(--brand);
}
.button:hover {
/* Derived states stay one perceptual step away */
background: oklch(from var(--brand) calc(l - 0.06) c h);
}
/* Wide-gamut bonus for capable displays */
@media (color-gamut: p3) {
:root { --brand: oklch(0.58 0.19 250); }
}- Keep tokens in OKLCH and let build tooling emit hex fallbacks (PostCSS
@csstools/postcss-oklab-functiondoes this automatically). - Name tokens by role (
--surface,--on-surface) rather than by color, so dark mode is a re-assignment, not a rewrite.
Recipe: migrate a hex palette#
- 1 - Convert losslessly. Paste the palette into the Converter (it handles hex/RGB/HSL/OKLCH in bulk, preserving CSS variable names). Conversion changes notation, not color.
- 2 - Read the diagnosis. Load the converted palette in the Lab. Legacy hand-picked ramps typically show a sagging lightness curve and chaotic hue drift - now visible as data.
- 3 - Regularize gently.Snap each step's L to an even ramp and unify hue drift, keeping chroma close to the original. The result usually differs by ΔE < 5 per step - invisible individually, dramatically more consistent as a system.
- 4 - Diff before adopting. Compare old and new in grayscale and in the contrast matrix; the regularized scale should only improve both.