Home / Snippets / Color & Theming /
Design system palette in OKLCH
10-step color scales with uniform lightness progression — one hue angle, infinite palette.
Quick implementation
:root {
/* Base hue — change this one value to generate a new palette */
--hue: 265;
/* 10-step lightness scale with uniform perceptual steps */
--color-50: oklch(0.97 0.02 var(--hue));
--color-100: oklch(0.93 0.04 var(--hue));
--color-200: oklch(0.85 0.08 var(--hue));
--color-300: oklch(0.77 0.12 var(--hue));
--color-400: oklch(0.69 0.16 var(--hue));
--color-500: oklch(0.61 0.19 var(--hue));
--color-600: oklch(0.52 0.19 var(--hue));
--color-700: oklch(0.44 0.17 var(--hue));
--color-800: oklch(0.35 0.13 var(--hue));
--color-900: oklch(0.27 0.09 var(--hue));
}
/* Usage */
.btn-primary { background: var(--color-600); color: var(--color-50); }
.btn-primary:hover { background: var(--color-700); }
.surface { background: var(--color-900); color: var(--color-100); }
.badge { background: var(--color-200); color: var(--color-800); }
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer building a design system color foundation.
Goal: Generate a 10-step color palette (50–900) from a single OKLCH hue angle, with perceptually uniform lightness steps and consistent chroma curves.
Technical constraints:
- Use oklch() for every color value — no hex, rgb(), or hsl().
- Lightness steps: 0.97, 0.93, 0.85, 0.77, 0.69, 0.61, 0.52, 0.44, 0.35, 0.27.
- Chroma peaks at the 500–600 range and tapers toward both extremes.
- Store the hue in a single --hue custom property so the entire palette can be re-themed by changing one value.
- Ensure 50–200 steps have sufficient contrast for dark text, and 600–900 for white text.
Framework variant (pick one):
A) Vanilla CSS custom properties on :root.
B) Tailwind v4 theme configuration — define the palette in @theme and extend with semantic aliases.
Edge cases to handle:
- Chroma clipping: some hue/chroma combos exceed the sRGB gamut — browser clamps automatically but verify in Chrome DevTools.
- Complementary palette: generate a second palette at hue + 180 for contrast/accent.
- Neutral palette: use chroma 0.01–0.02 for grays that have a subtle tint matching the brand hue.
- WCAG contrast: 500 is the pivot — pair 50–400 with dark text, 500–900 with white text.
Return CSS custom properties.
Why this matters in 2026
Design systems traditionally shipped hundreds of hand-tuned hex values. OKLCH changes the game: you define a single hue angle and derive the entire 10-step scale with uniform perceptual lightness. Changing the hue regenerates every shade instantly — no Figma re-export, no manual contrast checking. This is how Tailwind v4, Open Props, and modern design tokens work under the hood.
The logic
OKLCH separates color into three axes: lightness (0–1), chroma (saturation intensity), and hue (0–360). By fixing the hue and stepping lightness from 0.97 down to 0.27, you get a scale where each step looks equally spaced to the human eye — unlike HSL, where perceptual jumps are uneven. Chroma follows a bell curve, peaking at mid-lightness (500–600) and tapering at extremes, because very light and very dark colors can't sustain high saturation without clipping. The var(--hue) custom property makes the entire palette re-themeable with a single value change.
Accessibility & performance
The lightness scale is designed around WCAG contrast ratios: steps 50–400 pair with dark text (contrast ratio above 4.5:1), and steps 500–900 pair with white text. This eliminates guesswork when assigning foreground/background combinations. Performance-wise, custom properties resolve once at cascade time — there is no runtime cost compared to hardcoded values. The only caveat is gamut clipping: if a hue/chroma combination exceeds sRGB, the browser silently clamps it, which may shift the perceived color slightly. Use Chrome DevTools' color picker (it flags out-of-gamut values) to verify.