Home / Snippets / Color & Theming /
Vibrant palette
High-chroma oklch() swatches at consistent lightness across the full hue wheel — perceptually balanced and uniformly vivid.
Quick implementation
/* Vibrant OKLCH palette — consistent L and C, varied H */
:root {
--color-red: oklch(0.63 0.26 25);
--color-orange: oklch(0.70 0.22 55);
--color-yellow: oklch(0.84 0.20 95);
--color-green: oklch(0.72 0.22 145);
--color-cyan: oklch(0.74 0.18 200);
--color-blue: oklch(0.65 0.22 265);
--color-purple: oklch(0.62 0.24 305);
--color-pink: oklch(0.68 0.24 345);
}
/* Usage: accent text, badges, chart series, tags */
.badge--red { background: var(--color-red); color: oklch(1 0 0); }
.badge--green { background: var(--color-green); color: oklch(1 0 0); }
.badge--blue { background: var(--color-blue); color: oklch(1 0 0); }
.badge--purple { background: var(--color-purple); color: oklch(1 0 0); }
/* Tinted surface variant: lower lightness for background use */
.surface--red { background: oklch(0.22 0.06 25); }
.surface--green { background: oklch(0.22 0.06 145); }
.surface--blue { background: oklch(0.22 0.06 265); }
.surface--purple { background: oklch(0.22 0.06 305); }
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior design systems engineer building a color token layer for a dark-mode web application.
Goal: Create a vibrant, perceptually balanced color palette using oklch() — 8 hues at high chroma, consistent lightness, stored as CSS custom properties.
Technical constraints:
- Use oklch(L C H) syntax for all color values — no hex, hsl, or rgb.
- Target chroma C in the range 0.18–0.26; reduce C slightly at yellow (H ~95) where the gamut clips at high chroma.
- Adjust lightness L per hue to maintain perceptual brightness: yellows need higher L (~0.84), blues and reds need lower L (~0.62–0.65).
- Store each color as a --color-{name} custom property at :root.
- Provide a tinted surface variant per hue with L ~0.22 and C ~0.06 for dark-mode card backgrounds.
- White text (oklch(1 0 0)) on all vibrant swatches must meet WCAG AA contrast — verify via the OKLCH contrast picker.
Framework variant (pick one):
A) CSS custom properties at :root — consumed directly via var() throughout the stylesheet.
B) Design token JSON — export as { "color": { "red": { "value": "oklch(0.63 0.26 25)", "type": "color" } } } for Style Dictionary consumption.
Edge cases to handle:
- P3 gamut: oklch() colors with high chroma may be out of the sRGB gamut — use @media (color-gamut: p3) to apply the fullest chroma only in P3-capable displays, falling back to a slightly lower C value.
- Yellow contrast: oklch(0.84 0.20 95) on a white background fails contrast — document that yellow tokens are for dark backgrounds only.
- Color blindness: ensure no two adjacent hues are distinguishable only by hue (red vs green) without also differing in lightness or chroma.
- CSS custom property fallback: always supply a fallback hex in var(--color-red, #e8445a) for older toolchains that do not parse oklch().
Return CSS custom properties and usage examples.
Why this matters in 2026
HSL-based palettes have a fundamental problem: equal saturation values produce colors with wildly different perceived vibrancy across hues. A fully saturated yellow looks almost neon while an equally saturated blue appears muted and dark. OKLCH solves this with a perceptually uniform chroma axis — C 0.22 at hue 265 (blue) looks equally vivid to C 0.22 at hue 145 (green). This means designers can build a "vibrant row" of a palette by holding lightness and chroma constant and simply rotating the hue angle, rather than hand-tuning every value.
The logic
OKLCH represents color as Lightness (L), Chroma (C), and Hue (H). To build a vibrant row, hold L around 0.65–0.72 and C around 0.22 while sweeping H from 0 to 360. The only exception is yellow (H ~95), which has a narrower gamut at high chroma and must use a higher lightness (~0.84) and slightly lower chroma (~0.20) to avoid clipping. The result is a palette row where every swatch feels equally "full" to the human eye — something that is nearly impossible to achieve in HSL or RGB without individual value tuning.
Accessibility & performance
High-chroma colors can fail WCAG contrast requirements, so each vibrant token should be tested against its intended background before use as text. White text (oklch(1 0 0)) on the provided vibrant backgrounds passes AA contrast for most hues on dark surfaces, but yellow swatches should be reserved for graphical indicators rather than text. For users in high-contrast mode, browser forced-colors will override custom property values — test with @media (forced-colors: active) to ensure the UI remains usable. Because these are CSS custom properties, there is zero runtime cost: the browser resolves them at cascade time with no JavaScript involved.