Skip to content

Design Systems

OKLCH Color Palettes — Perceptually Uniform Design Systems for 2026

Why Your HEX Colors Look Wrong in Dark Mode

🎨 👁️ 📐
## The Problem With HEX (and Why You Didn't Know) When you design a color palette in Figma, you pick a hex value like `#4F46E5` (indigo-600) and move on. On a light background it looks right. Switch to dark mode, make it lighter with `#6366F1` (indigo-500), and suddenly it looks *worse* — more washed out, less vibrant, harder to read. You tweak it again. Again. Never satisfied. The culprit: HEX and RGB are *perceptually non-uniform*. They're device coordinates, not human-eye coordinates. When you ask for "10% lighter", RGB adds equal amounts of red, green, and blue. But your eye doesn't work that way. A 10% brightness bump in one color space can feel like a 20% pop in another. Designers have known this for 15 years. Web developers are only now paying attention. OKLCH is the fix. It's a color space designed for human perception: O = lightness (0–100, which humans actually understand), K = saturation (0–100), C = hue (0–360°). Change the L value and colors lighten/darken the way you expect. Change saturation and colors get richer or muted consistently. Hue stays hue. Tailwind 4 defaults to OKLCH. So does the Aidxn brand system. If you're not using it yet, you're colour-grading in the dark. ## Why HEX/RGB Fail in Dark Mode Imagine you have a blue palette: - Light mode: `#4F46E5` (dark enough to read on white) - Dark mode: You need it lighter. `#6366F1` looks right at 2am. `#818CF8` is even better. But here's the trap: those three hex values don't sit on a perceptually uniform scale. The jump from `#4F46E5` to `#6366F1` feels different from the jump from `#6366F1` to `#818CF8`. Your eye is doing nonlinear math. The designer's eye noticed. Your users' eyes noticed. But your code doesn't know. In RGB: `#4F46E5` = rgb(79, 70, 229). `#6366F1` = rgb(99, 102, 241). `#818CF8` = rgb(129, 156, 248). The math is linear — each hex gets 20 and 30 units added. But perceptually? Not linear at all. In OKLCH, the same palette looks like: - `oklch(49% 0.22 262°)` — dark indigo - `oklch(60% 0.17 262°)` — medium indigo - `oklch(72% 0.13 262°)` — light indigo The L values are 49, 60, 72. That's the scale your eye actually experiences. Change L by 10 units, get a perceptually equivalent step every time. Hue stays locked at 262°. Saturation drops slightly as you lighten (which happens naturally because highly saturated light colors burn your eyes). The palette works across light and dark mode without hand-tweaking. ## How OKLCH Actually Works OKLCH has four dimensions: - **L (Lightness):** 0–100, where 0 is black and 100 is white. Unlike RGB where "brightness" mixes three channels, L directly maps to "how light is this". An L of 50 is perceptually halfway between black and white. - **C (Chroma):** 0–0.4+ (unbounded, but practical range is 0–0.3). Think saturation, but device-independent. Higher chroma = richer, more vibrant color. - **H (Hue):** 0–360°. Red is 30°, yellow is 90°, green is 150°, blue is 270°, etc. Pure hue, independent of brightness or saturation. The genius: to create a palette, you lock H and C, then vary L. You get a shade that gets darker and lighter the way humans perceive it. Aidxn's primary brand color in OKLCH: `oklch(57% 0.18 251°)`. That's vibrant purple at medium brightness. To make a 9-step palette: - Darkest (900): `oklch(25% 0.18 251°)` - Dark (800): `oklch(35% 0.18 251°)` - Regular (600): `oklch(57% 0.18 251°)` - Light (300): `oklch(80% 0.15 251°)` ← saturation drops slightly (important for readability) - Lightest (100): `oklch(95% 0.05 251°)` ← nearly desaturated (for subtle backgrounds) Same hue the whole way. L goes 25 → 35 → 45 → 57 → 70 → 80 → 90 → 95. Linear steps. The colors feel like a coherent family. No hand-tweaking. No "why does this shade look wrong". ## Building a Palette Without Guessing Two approaches: **Option 1: Perceptually Uniform Scaling (the Tailwind way).** Pick your base color (e.g., your brand primary), convert it to OKLCH, lock H and C, then generate 9 lightness levels at equal intervals. Tailwind 4's color system does this automatically. You define: ```oklch @theme { --color-primary-50: oklch(97% 0.05 251°); --color-primary-100: oklch(94% 0.08 251°); --color-primary-200: oklch(90% 0.11 251°); --color-primary-300: oklch(84% 0.13 251°); --color-primary-400: oklch(76% 0.15 251°); --color-primary-500: oklch(65% 0.17 251°); --color-primary-600: oklch(57% 0.18 251°); --color-primary-700: oklch(48% 0.17 251°); --color-primary-800: oklch(38% 0.15 251°); --color-primary-900: oklch(25% 0.10 251°); } ``` Slight saturation drop as you go lighter and darker (natural for human eyes; overly saturated whites and blacks cause eye strain). Linear L steps. **Option 2: Convert from Existing Hex.** You have a brand color locked to hex (like Aidxn does). Use a converter (Polished, Color.js, or online tools like oklch.com): - Paste your hex: `#4F46E5` - Get the OKLCH equivalent: `oklch(49% 0.22 262°)` - Then scale from there Once you have H and C, generate the 9-step palette by varying L from 20–95. ## Contrast Ratios & Accessibility OKLCH makes contrast audits easier. Two colors with a 40+ point L difference have excellent contrast. `oklch(80% ... )` on `oklch(30% ... )` is always WCAG AAA. You don't need a color-pair contrast checker — the L value does the work. Aidxn uses `L >= 40` between text and background. White text on `oklch(35% ...)` = safe. Dark text on `oklch(90% ...)` = safe. Black text on `oklch(70% ...)` = 50 point gap, overkill but readable. Check WCAG ratios, but L spacing will get you 90% there. ## Six FAQs **Q: Will OKLCH replace HEX?** A: No. HEX is going nowhere. OKLCH and sRGB both exist in every modern browser. Use OKLCH for design systems and palettes. Use HEX for one-off values where you don't care about scaling. **Q: Do I need to convert my entire site to OKLCH?** A: Not at all. Tailwind 4 defaults to OKLCH but still accepts hex, rgb, hsl, and other formats. Gradual migration is fine. We moved Aidxn brand tokens to OKLCH in brand.json and left everything else alone. **Q: What if my brand color is a specific hex?** A: Convert it to OKLCH once, lock H and C, and build your palette from there. The OKLCH version will be *slightly* different from the hex (usually 2–3% perceptual difference), but it's negligible. If you need exact hex fidelity, use `color(display-p3 ... )` or `color(srgb ... )` functions — they let you pin precise hex values in a perceptually uniform space. **Q: Does OKLCH work in all browsers?** A: Yes. Every browser shipped OKLCH support between 2023–2024. IE 11 doesn't support it, but you're not targeting IE 11 in 2026. Use a fallback hex if you need legacy support — browsers ignore the oklch() if they don't understand it. **Q: How does OKLCH compare to HSL?** A: HSL is older and perceptually non-uniform. An H of 0° with L 50% is red. H of 0° with L 80% is a light pink, but it *looks* less saturated than the L 50% version even if S is locked. OKLCH fixes this. HSL still works, but OKLCH is measurably better for design systems. **Q: Can I use OKLCH in Figma?** A: Figma uses sRGB hex under the hood, but you can convert. Paste the OKLCH value into a color tool, get the hex equivalent, use that in Figma. Going the other way: export your Figma palette as hex, convert to OKLCH for your CSS, then sync both. A little tedious but doable. ## The Verdict OKLCH is the reason your dark mode palettes look off. HEX and RGB are device coordinates, not human ones. When you lock hue and saturation and vary lightness, colors scale the way your eye expects. Tailwind 4 made OKLCH the default because it's simply better. The Aidxn brand system uses it. Your design system should too. If you're building a new palette in 2026, start in OKLCH. If you're inheriting a hex palette, convert it once and build from there. The 20 minutes of setup saves hours of colour-tweaking and dark-mode regret. Check out our full pricing page to see how we structure design systems — we blend OKLCH tokens with Tailwind 4. For more on our frontend stack, see our Tailwind v4 migration guide. The best colour is the one that scales.
Let us make some quick suggestions?
Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.