Engineering
Why I Abandoned HEX and HSL for OKLCH in Tailwind v4
Engineering color for perceptual uniformity, accessibility, and the P3 display gamut.
The Illusion of HSL
We have been lying to ourselves about color math.
For over two decades, web developers have relied on HEX codes and RGB values. They are the absolute standard, yet they share a fundamental flaw: they are designed for machines, not for human eyes.
When we realized that hexadecimal strings like #2D4F3C were impossible to manipulate programmatically, we shifted to HSL (Hue, Saturation, Lightness). It felt like an upgrade. We could finally write hsl(200, 50%, 50%) and intuitively tweak the lightness by changing the last percentage.
Except, HSL is mathematically broken when it comes to human perception.
The Accessibility Roulette
In HSL, a pure yellow at 50% lightness looks almost white to the human eye. A pure blue at 50% lightness looks nearly black. If you build a dynamic UI theme relying on HSL lightness for contrast ratios, you are essentially guessing.
When architecting digital platforms for educational institutions, accessibility isn't an optional feature—it’s a baseline requirement. A visually impaired parent navigating a school dashboard cannot be the victim of a mathematical color flaw.
Enter OKLCH
A color space built for human perception.
To solve this, CSS Color Level 4 introduced OKLCH. It stands for Lightness, Chroma, and Hue, based on the Oklab color space.
The revolution of OKLCH lies in a concept called Perceptual Uniformity.
L: Lightness (0 to 1)
Unlike HSL, 0.6 lightness in OKLCH is identically bright to the human eye, regardless of whether the color is neon green or deep purple.
C: Chroma (0 to 0.4+)
A measure of color intensity. It prevents the 'muddy gray' desaturation problem found in traditional web color models.
H: Hue (0 to 360)
The angle on the color wheel. Because the space is uniform, rotating the hue creates perfectly smooth, realistic gradients.
If I define my text color to have a lightness of 0.25 and my background a lightness of 0.98, I am mathematically guaranteed to pass WCAG contrast checks—no matter what hue I inject into my theme.
HSL — 50% Lightness
Non-uniformSame mathematical lightness, wildly different perceived brightness
OKLCH — 0.6 Lightness
Perceptually uniformSame perceptual lightness, identical perceived brightness
HSL blue at 50% lightness appears 3.4× darker than HSL yellow at 50% lightness. In OKLCH, all five colors share identical perceived brightness.
Breaking the sRGB Barrier
Hardware has evolved. Our code should too.
There is a second reason why I migrated this portfolio’s "Editorial Tech" design system entirely to OKLCH.
Modern devices—from the iPhone in your pocket to the MacBook Pro—use OLED screens or displays capable of rendering the Display P3 color gamut. P3 contains roughly 25% more colors than the legacy sRGB gamut (which HEX and HSL are locked into).
35%
Legacy sRGB
of visible human spectrum
+25%
Display P3
Wider color volume
95%+
CSS Support
Global browser adoption
The deep forest green and the warm beige you see on this website cannot be accurately represented in a HEX code. They rely on the extended chroma available in OKLCH. By using this color space, the browser automatically utilizes the P3 gamut if the user's hardware supports it, gracefully falling back to sRGB if it doesn't.
Implementation in Tailwind v4
Clean variables, infinite opacities.
With the release of Tailwind CSS v4, the framework moved away from complex JavaScript configurations to a CSS-first @theme engine. This makes implementing an OKLCH design system incredibly elegant.
Instead of fighting with plugins, we define our semantic tokens as raw OKLCH coordinates in globals.css. Tailwind v4 is smart enough to parse these and automatically generate utility classes with opacity modifiers (like bg-primary/50).
@import "tailwindcss";
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
/* Tailwind v4 automatically handles opacity modifiers */
}
:root {
/* OKLCH Format: oklch(Lightness Chroma Hue) */
--background: oklch(0.99 0.005 80); /* Warm Alabaster */
--foreground: oklch(0.25 0.01 160); /* Deep Anthracite */
--primary: oklch(0.42 0.04 155); /* Deep Forest Green */
}
.dark {
--background: oklch(0.20 0.01 160);
--foreground: oklch(0.98 0.005 80);
--primary: oklch(0.65 0.05 155);
}Notice the strict logic: To create the dark mode, I didn't have to guess new colors. I simply inverted the Lightness value while keeping the Chroma and Hue identical. The math ensures the contrast remains perfectly accessible.
The Verdict
Engineering over guessing.
As a Digital Experience Architect, my goal is to eliminate friction—both for the user and for the developer maintaining the system years down the line.
Migrating to OKLCH isn't just a design trend. It is a fundamental shift from treating color as a visual art to treating it as an engineering primitive. It guarantees accessibility, unlocks the full potential of modern hardware, and drastically simplifies the creation of dynamic, inclusive UI themes.
“We shouldn't guess what is legible. We should calculate it. OKLCH gives us the mathematical vocabulary to write color as code.
Curious about the implementation?
Explore the OKLCH configuration and the Tailwind v4 setup in the source code of this portfolio.