perceptually uniform palettes and P3 wide gamut
Tailwind CSS v4 has switched its entire default color palette to OKLCH, by far the best color space for perceptually uniform, predictable color palettes on the web. This guide explains how OKLCH works, how to build your own palettes with it, and how Lightning CSS automatically ensures browser compatibility.
Table of Contents
- 1. Why OKLCH? The problem with HSL and hex
- 2. Understanding OKLCH: lightness, chroma, hue
- 3. The new color palette in Tailwind CSS v4
- 4. Building your own OKLCH color palette
- 5. P3 wide gamut: more vivid colors on modern displays
- 6. Lightning CSS as an automatic OKLCH transformer
- 7. Integrating OKLCH into @theme and design tokens
- 8. HSL vs. OKLCH in direct comparison
- 9. Color interpolation and gradients in OKLCH
- 10. Summary
- 11. FAQ
1. Why OKLCH? The problem with HSL and hex
Anyone who develops color palettes for websites knows the problem: in HSL, colors with identical lightness values look visually completely different in brightness. An hsl(120, 100%, 50%) (green) appears noticeably brighter than an hsl(240, 100%, 50%) (blue), even though both share the same L value. That is because HSL is based on the physical RGB color space rather than on how the human eye perceives brightness. The result: palettes with "even" steps that look visually uneven, and contrast ratios that do not match the expected value in reality.
Hex values such as #3b82f6 (Tailwind blue-500) do not solve the problem either, they are just a compact notation for RGB and share all of its perceptual issues. The OKLCH color space (OK Lightness Chroma Hue) was specifically designed to solve these perceptual problems. OKLCH is a perceptually uniform color space: two colors with the same L value actually look equally bright to the human eye. That makes color palettes more predictable and contrast ratios more reliable. That is exactly why Tailwind CSS v4 switched its entire default palette to OKLCH.
The difference becomes particularly clear with color palettes that have 11 steps (50 to 950). In the old HSL-based Tailwind palette, the lightness differences between steps were inconsistent, especially for blue and violet, where the eye barely perceives small brightness differences. In the new OKLCH color palette of Tailwind v4, the lightness steps are calculated so that each step has an equally sized perceived brightness difference from the next. That is the central promise of OKLCH in practical design.
2. Understanding OKLCH: lightness, chroma, hue
The OKLCH syntax has three parameters: oklch(L C H). L stands for lightness and has a value range from 0 (black) to 1 (white), alternatively as a percentage from 0% to 100%. C stands for chroma (saturation) and ranges from 0 (grey) up to a maximum that depends on the hue axis, typical values lie between 0.1 and 0.4. H stands for hue as a degree angle from 0° to 360°, corresponding to the color wheel: 0° is red, 120° is green, 240° is blue. Opacity can optionally be added as a slash value: oklch(60% 0.2 250 / 0.5).
The most important concept when working with OKLCH is the decoupled lightness model. In HSL, increasing the saturation value often changes the perceived brightness. In OKLCH, lightness and chroma are independent: you can increase the chroma (making it more vivid) without changing the perceived brightness. That makes it possible to build color palettes by simply varying the lightness axis, without having to manually correct the saturation. For a practical feel: oklch(50% 0.2 250) is a medium, saturated blue. oklch(80% 0.1 250) is a light, desaturated blue. oklch(30% 0.3 250) is a dark, saturated blue.
3. The new color palette in Tailwind CSS v4
Tailwind CSS v4 delivers its entire default palette as OKLCH values. That means: all classes like bg-blue-500, text-sky-700 or border-emerald-400 now reference internally computed OKLCH values. The visual colors look similar to v3 for most users, but the underlying values are calculated in a fundamentally different way. On modern displays with P3 wide gamut, current iPhones, iPads, MacBooks with Retina display, many Android flagships, the colors in v4 are more vivid and luminous than in v3, because OKLCH can express values that go beyond the sRGB color space.
The consequence for projects migrating from Tailwind v3 to v4: the colors may look slightly different even though the same classes are used. That is not a bug, but the intended result of the OKLCH switch. Visual regression tests should be explicitly designed for color differences, a plain pixel diff will show many "differences" that actually just reflect the improved color rendering. Anyone who wants to keep the old colors exactly as they were can define the v3 values as CSS custom properties in @theme and thereby override the v4 default values.
/* Tailwind v4 internal OKLCH color definitions (simplified) */
/* These are generated automatically, shown here for understanding */
@theme {
/* sky-500 in OKLCH, perceputally uniform brightness */
--color-sky-500: oklch(68.5% 0.169 237.323);
--color-sky-600: oklch(60.1% 0.172 237.323);
--color-sky-700: oklch(53.5% 0.165 244.3);
/* emerald-500, same L value as sky-500, same perceived brightness */
--color-emerald-500: oklch(69.6% 0.187 162.48);
/* Custom brand palette using OKLCH */
--color-brand-50: oklch(97% 0.02 250);
--color-brand-100: oklch(93% 0.04 250);
--color-brand-200: oklch(87% 0.07 250);
--color-brand-300: oklch(78% 0.12 250);
--color-brand-400: oklch(68% 0.17 250);
--color-brand-500: oklch(58% 0.21 250);
--color-brand-600: oklch(49% 0.20 250);
--color-brand-700: oklch(41% 0.18 250);
--color-brand-800: oklch(32% 0.14 250);
--color-brand-900: oklch(23% 0.09 250);
--color-brand-950: oklch(15% 0.05 250);
}
/* Usage, exactly the same Tailwind classes, OKLCH values underneath */
/* .bg-brand-500 { background-color: oklch(58% 0.21 250); } */
/* .text-brand-700 { color: oklch(41% 0.18 250); } */
4. Building your own OKLCH color palette
Building your own OKLCH color palette for a Tailwind v4 project is simpler than it sounds. The pattern is always the same: pick a hue value (the brand's color tone), a chroma range (how saturated the colors should be), and vary the lightness axis from light (97%) to dark (15%) in even steps. The result is a palette with 11 steps in which each step has the same perceived brightness difference from the next.
The tool oklch.com lets you interactively adjust OKLCH values and immediately see how the color looks on screen. For building color palettes there is also the tool uicolors.app, which generates a complete OKLCH palette in Tailwind style from a single hex value. These palettes can be adopted directly as an @theme block in the Tailwind configuration. The decisive advantage: because OKLCH is perceptually uniform, the generated palettes are inherently more consistent than palettes built manually in HSL or hex.
There is an important limit when choosing chroma: if the chroma is set so high that the resulting color value lies outside the sRGB gamut, it gets clipped on non-P3 displays, that is, rounded to the closest possible sRGB value. That is not necessarily a problem, but it means the color looks less saturated on older displays than on P3 displays. For web projects that want to ensure sRGB compatibility, chroma should be kept below 0.20 for the 500-level steps.
5. P3 wide gamut: more vivid colors on modern displays
The sRGB color space, on which hex and HSL are based, covers only part of the colors that modern displays can show. The Display P3 profile, standard on current Apple displays, many high-end Android displays and newer Windows monitors, can render about 25% more colors than sRGB. These additional colors lie mainly in the intense green and red range. With OKLCH, colors that lie within the P3 gamut can be defined directly in CSS and appear noticeably more luminous on capable displays.
The browser support picture for P3 in CSS is good in 2026: Chrome, Firefox and Safari natively support display-p3 colors and the OKLCH syntax. For browsers without P3 support, the automatic fallback system kicks in: Lightning CSS in Tailwind v4 automatically generates sRGB fallback values for all OKLCH colors that lie outside the sRGB gamut. Developers therefore do not need to worry about browser compatibility, the tool handles it transparently.
/* P3 Wide Gamut colors in OKLCH, more saturated on capable displays */
@theme {
/* High chroma values exceed sRGB, Lightning CSS adds sRGB fallback */
--color-vivid-green: oklch(68% 0.28 145);
--color-vivid-blue: oklch(58% 0.27 265);
--color-vivid-red: oklch(55% 0.28 28);
}
/* Lightning CSS automatically generates this for older browsers: */
/*
@supports not (color: oklch(0 0 0)) {
:root {
--color-vivid-green: #00c060; /* closest sRGB equivalent */
--color-vivid-blue: #006ff5;
--color-vivid-red: #e5002b;
}
}
*/
/* Manual P3 media query (optional, Lightning CSS handles this) */
@media (color-gamut: p3) {
:root {
/* P3-specific overrides for even more vibrant colors */
--color-vivid-green: oklch(72% 0.32 145);
}
}
/* Usage in gradient, OKLCH interpolation is perceptually uniform */
.hero-gradient {
background: linear-gradient(
in oklch,
oklch(60% 0.2 240),
oklch(60% 0.2 300)
);
}
6. Lightning CSS as an automatic OKLCH transformer
One of the biggest practical advantages of Tailwind CSS v4 is the integration of Lightning CSS, which automatically transforms all OKLCH colors for older browsers. Anyone who writes oklch(58% 0.21 250) in their CSS automatically gets an sRGB fallback such as color: #3b82f6 generated by Lightning CSS as a preceding declaration, which is used by browsers without OKLCH support. That means: progressive enhancement for colors is fully automated.
Lightning CSS uses a mathematically precise gamut-mapping algorithm that computes the closest possible sRGB value for a given OKLCH color. The result is noticeably better than simple clipping: instead of cutting off the saturated portion of the color, the chroma is progressively reduced until the color lies within the sRGB gamut. This preserves the perceived brightness and hue better than older methods. In practice, an sRGB user therefore sees a very similar, if somewhat less saturated, color than a P3 user.
7. Integrating OKLCH into @theme and design tokens
Integrating OKLCH colors into Tailwind CSS v4 via @theme is direct and straightforward. The syntax is the same as for other color tokens, the only difference is that the value is given as an OKLCH function instead of hex or HSL. Tailwind processes the values transparently: the generated utility classes (bg-brand-500, text-brand-700, etc.) reference the OKLCH values internally. Lightning CSS adds the browser fallbacks automatically.
For projects importing from Figma or other design tools, the design tool's colors (usually as hex or HSL) need to be converted to OKLCH. The tool oklch.com offers a conversion function. Alternatively, the conversion step can be automated: a short Node.js script using the library culori converts all hex values in a token JSON file to OKLCH and writes the result as an @theme block into the CSS file. This fully automates the workflow from design tool to Tailwind OKLCH token.
8. HSL vs. OKLCH in direct comparison
The following table shows the most important differences between HSL and OKLCH for practical work with Tailwind CSS.
| Aspect | HSL | OKLCH | Meaning |
|---|---|---|---|
| Perceptual uniformity | Not given | Given (Oklab-based) | Palettes look even |
| P3 wide gamut | sRGB only | Up to P3 and beyond | More luminous colors on P3 displays |
| Gradient interpolation | Grey cast in the middle | Even transitions | Nicer color gradients |
| Browser support | Universal | Modern + fallback via CSS | Lightning CSS generates fallbacks |
| Tailwind v4 default | No (v3 was HSL) | Yes (v4 default) | No configuration needed |
9. Color interpolation and gradients in OKLCH
One of the technically most interesting properties of OKLCH is its behavior during color interpolation, that is, in gradients and animations. In sRGB and HSL, gradients between two saturated colors (for example blue to green) often produce a grey or desaturated area in the middle of the transition. This "grey belly" occurs because the interpolation passes through the midpoint of the color space, where there is little saturation. In OKLCH, the interpolation runs along the chroma axis instead of through the center of the color space, the result is gradients that stay saturated across their entire length.
In CSS, OKLCH interpolation is enabled via the in oklch syntax in the linear-gradient() function: linear-gradient(in oklch, oklch(60% 0.2 240), oklch(60% 0.2 120)). The result is a blue-to-green transition that stays saturated across its entire length, without the typical grey cast in the middle. The same applies to animations with CSS transitions and Tailwind CSS: when a color animates from OKLCH to OKLCH, the browser interpolates in OKLCH and the result is a perceptually uniform crossfade. This is especially visible in dark mode transitions and theme switching animations.
/* OKLCH gradient interpolation, no desaturated midpoint */
.gradient-oklch {
/* in oklch keeps saturation uniform across the entire gradient */
background: linear-gradient(
135deg in oklch,
oklch(55% 0.22 265), /* vibrant blue */
oklch(55% 0.22 145) /* vibrant green */
);
}
/* Compare: sRGB gradient has grey midpoint */
.gradient-srgb {
/* without "in oklch", interpolation goes through grey */
background: linear-gradient(
135deg,
oklch(55% 0.22 265),
oklch(55% 0.22 145)
);
}
/* OKLCH in Tailwind @theme for full color scale */
@import "tailwindcss";
@theme {
/* Brand color at hue 265 (blue-violet), 11 OKLCH steps */
--color-violet-50: oklch(97% 0.01 265);
--color-violet-100: oklch(93% 0.03 265);
--color-violet-200: oklch(87% 0.06 265);
--color-violet-300: oklch(79% 0.11 265);
--color-violet-400: oklch(70% 0.16 265);
--color-violet-500: oklch(60% 0.21 265);
--color-violet-600: oklch(51% 0.20 265);
--color-violet-700: oklch(43% 0.18 265);
--color-violet-800: oklch(34% 0.14 265);
--color-violet-900: oklch(25% 0.09 265);
--color-violet-950: oklch(17% 0.05 265);
}
10. Summary
The OKLCH color space is the most significant advance in CSS color science since the introduction of HSL. Tailwind CSS v4 turns it into everyday reality for every frontend developer using Tailwind by switching the default palette and integrating Lightning CSS. The practical benefits are concrete: perceptually uniform palettes that look consistent at every step; more luminous colors on P3-capable displays; clean gradient interpolation without a grey middle; and automatic browser compatibility through Lightning CSS fallbacks.
For your own design tokens in @theme, the OKLCH pattern is the same as for hex or HSL, just with better predictability and P3 support. Anyone who builds color palettes in OKLCH gets inherently more consistent results than with palettes built manually in HSL. The tool ecosystem, oklch.com, uicolors.app, the culori library, is mature and makes getting started easy. OKLCH is not a theoretical improvement, it is a practical benefit for every Tailwind project.
Tailwind CSS OKLCH Color Space, the essentials at a glance
OKLCH syntax
oklch(L% C H): lightness 0 to 100%, chroma 0 to 0.4+, hue 0 to 360°. Perceptually uniform: same L equals same perceived brightness.
P3 wide gamut
High chroma values in OKLCH exceed sRGB. More luminous colors on P3 displays. Lightning CSS generates sRGB fallback automatically.
Tailwind v4 default
Entire default palette switched to OKLCH in v4. @theme accepts oklch() values directly. Utilities like bg-sky-500 show OKLCH internally.
Gradient interpolation
linear-gradient(in oklch, ...) prevents the grey midpoint of sRGB interpolation. Saturated transitions with no quality loss in the middle.