It is 2am. You are seven browser tabs deep into a color picker. Your designer sent over a Figma file with a gorgeous blue for the primary button. You translated it to #4A90D9, and it looked perfect. Then you toggled dark mode. And now your button looks like it was fished out of a swamp.
So you do what every frontend developer has done since the dawn of CSS: you nudge the hex value. #4A90D9 becomes #5A9FE8. Better? No, too washed out. #4C95E0? Warmer, but now it clashes with the accent. #5191D4? You have been staring at swatches so long that you have lost all sense of what blue even is. You are color-blind now. Congratulations.
This, my friends, is The 2am Hex Nudge. We have all performed this ritual. Some of us perform it weekly. And I am here to tell you: it is not your fault. HEX codes are gaslighting you.
HEX and HSL: A Beautiful Lie
Here is a fun party trick (assuming you go to parties where people discuss color spaces — and if you are reading this, let us be honest, you do). Take two HSL colors with the same lightness: hsl(60, 100%, 50%) (yellow) and hsl(240, 100%, 50%) (blue). According to the math, they are equally light. According to your actual human eyeballs, the yellow is a screaming toddler at a restaurant and the blue is hiding under a table at the same restaurant.
Same L value. Wildly different perceived brightness. HSL said they were twins. Your retinas said one of them is adopted.
This is because HSL models how monitors mix light, not how your brain processes it. It is like measuring two songs by their file size and concluding they are equally loud. Technically a metric. Practically useless.
And HEX? HEX is just HSL wearing a trench coat made of hexadecimal. Same underlying math. Same lies. Fancier outfit.
OKLCH: The Color Space That Went to Therapy
OKLCH stands for OK Lightness Chroma Hue — and yes, it literally starts with "OK" because it is the first color space that is actually okay at its job. (I did not name it. But I respect whoever did.)
It is a perceptually uniform color space. In normal-person language: if two OKLCH colors have the same L value, they genuinely look equally bright to a human being. Not "equally bright according to math that was invented for cathode ray tubes." Actually, physically, to-your-eyeballs equally bright.
The three channels:
- L (Lightness): 0 is black, 1 is white. And here is the magic — going from 0.5 to 0.6 looks like the same-size jump whether you are looking at red, blue, yellow, or that weird chartreuse your designer insists on using. No more "why is yellow brighter than blue" existential crises.
- C (Chroma): How vivid the color is. Zero is a grayscale monk who has renounced worldly saturation. Crank it up and you get punchy, saturated brilliance. Critically, chroma does not mess with perceived lightness. They are independent. Like adults.
- H (Hue): The color wheel angle, 0 to 360. Same concept as HSL, but because L and C are well-behaved, rotating the hue does not randomly detonate your palette. In HSL, changing the hue could secretly change the brightness. OKLCH does not play those games.
Tailwind v4 Said "We Are Done Pretending"
When Tailwind CSS v4 dropped, the team made a decision that most people scrolled past in the changelog: the entire default color system is now OKLCH. Every shade of every color. This is not a checkbox feature. This is a philosophical stance.
In our own codebase, we took the hint. Open globals.css and you will see lines like --primary: oklch(0.4891 0 0) where the old version had --primary: #7c7c7c. Looks like a format change. Feels like a format change. It is not a format change. It is the difference between a hand-drawn map and GPS.
40 Themes, Zero 2am Hex Nudges
Here is where it gets fun. We ship a color scheme selector with 40+ complete themes — and we did not hand-craft a single one. They all come from tweakcn.com, an open-source Shadcn theme editor that generates beautifully balanced OKLCH palettes. Shoutout to the tweakcn team for doing the hard design work so we did not have to. Each theme is defined as a set of OKLCH triples: background, primary, and accent — in both light and dark variants. All stored in a single TypeScript object. No design tokens platform. No paid Figma plugin. Just community-made themes that make sense.
Take amber-minimal. Its light primary is oklch(0.7686 0.1647 70.0804). Looks like a robot sneezed? Let me translate. L of 0.77: bright and airy. C of 0.16: rich saturation without looking like a highlighter. H of 70: warm amber territory. Three numbers, one complete story.
Now here is the kicker. The dark variant uses the exact same primary. Same L, same C, same H. The only thing that changes is the background — from oklch(1 0 0) (white) to oklch(0.2046 0 0) (near-black). And it just works. The amber looks gorgeous on both. No nudging. No 2am. No existential dread.
With HEX, switching backgrounds meant re-tuning every accent by hand. It was like redecorating your apartment and discovering that your couch, which looked great against white walls, now looks cursed against dark gray. OKLCH is the couch that matches everything. We all deserve that couch.
Generating Palettes Without Losing Your Mind
The absolute killer feature of OKLCH for palette work: you can lock any channel and sweep the others, and the results are predictable. Want five shades of your brand blue that all feel equally vibrant? Lock the chroma and hue, step the lightness from 0.95 to 0.25. Done. Every shade is perceptually consistent because the math actually respects human vision for once.
Need a hover state? Bump L by 0.05. Disabled state? Drop C toward zero — the color gracefully desaturates without shifting hue or brightness. Active state? Nudge L down by 0.1. These operations are boring. Predictable. Unsurprising. And after years of The 2am Hex Nudge, "boring and predictable" is the most beautiful phrase in the English language.
Quick aside: remember that designer Figma file from the intro? With OKLCH, you can take their exact primary color, derive every state variant mathematically, and know they will look correct. No round-trip to the designer. No "can you check this in dark mode?" Slack message at 11pm. Just math. Sweet, emotionally stable math.
Your Monitor Can See Colors HEX Cannot
Plot twist time. Every iPhone since the 7, every MacBook since 2016, and most recent Android flagships support the Display P3 color gamut — roughly 50% larger than sRGB. That means your users' screens can show colors that HEX codes literally cannot express. You have been leaving colors on the table. Vivid corals, electric teals, saturated magentas — all locked behind a door that HEX does not have the key to.
OKLCH walks right through that door. When we write oklch(0.6726 0.2904 341.4084) for our cyberpunk theme, P3 displays render the full electric magenta. On older sRGB screens, the browser automatically gamut-maps it to the closest representable color. No @supports queries. No fallback chains. No "I will just add a comment saying this might look different on some devices" cope. It. Just. Works.
Meanwhile, HEX is still stuck in a color space invented when monitors weighed forty pounds. It is like sending faxes in the age of Slack.
The Theme Swap That Made Me Emotional
This is where it all comes together, and honestly, this is the part that made me unreasonably happy the first time I saw it work.
Our theme system is pure CSS. Each of our 40+ color schemes is a standalone CSS file. Each file redefines the same set of OKLCH-valued custom properties under :root for light mode and html[data-theme="dark"] for dark mode. Tailwind v4 maps these to utility classes through @theme inline. When a user picks a theme, we set a cookie and swap the CSS import. That is it.
Every bg-primary, every text-foreground, every border-accent across the entire application updates instantly. No JavaScript color manipulation. No runtime recalculation. No React re-renders. Just CSS doing what CSS was born to do, while JavaScript takes a well-deserved nap.
Forty themes — all sourced from tweakcn.com. Zero lines of color-switching JavaScript. We dropped in a new tweakcn palette last week and had a working theme in twelve minutes. Twelve. I used to spend twelve minutes just on the hover state of a single button. The 2am Hex Nudge is dead, and I danced on its grave.
"But I Have 400 HEX Values in My Codebase"
I hear you. The migration sounds terrifying. It is not. If you are already on Tailwind v4, surprise: you are using OKLCH whether you know it or not. The default palette is defined in it. The only thing left is converting your custom colors.
Here is the game plan:
- Convert CSS variables first. Paste your HEX values into oklch.com and grab the OKLCH equivalents. The visual output is nearly identical — your designer will not notice. (Do not tell them I said that.)
- Replace the values in your globals.css or theme files. Ship it. Nothing looks different yet, and that is the point.
- Start using the perceptual uniformity. Generate hover, focus, and disabled states by adjusting L and C instead of opening a color picker like an animal.
- Go wild with P3. Crank the chroma past what sRGB allows. Watch your colors wake up on modern displays. Feel alive.
The whole process took us an afternoon. And unlike most "quick migrations," nothing caught fire.
The Bottom Line (No, Really, The Actual Bottom)
HEX codes are a relic of how monitors work, not how humans see. HSL tried to be friendlier and ended up being the friend who says "I am fine" when they are clearly not fine. OKLCH is the color space that finally went to therapy, did the work, and came back as a functional adult.
In Tailwind v4, OKLCH is the foundation of dynamic theming, accessible contrast ratios, P3-wide color, and palettes that survive the jump from light to dark mode without human intervention.
So the next time you find yourself hunched over a color picker at 2am, nudging a hex value one digit at a time, whispering "please look right in dark mode" — stop. Close the color picker. Open your CSS file. Write oklch(). And go to bed at a reasonable hour. Your eyes, your designer, and your dark mode users will all thank you.
The 2am Hex Nudge is over. Long live OKLCH.

