Modern CSS Color Functions: A 2026 Reference
oklch(), color-mix(), relative color syntax, and color() — what shipped, what it replaces, and the syntax you'll actually reach for.
CSS color syntax went through more real change in the last few years than in the previous two decades combined. If the last time you closely read the CSS color spec was when rgba() felt new, here's what's actually shipped in browser engines and what problem each addition solves.
oklch() and oklab() — perceptually uniform color, natively
.button {
background: oklch(62% 0.19 259);
color: oklch(98% 0.01 259);
}The three arguments are Lightness (0–100%), Chroma (roughly 0 to 0.4 for in-gamut sRGB colors, though the format supports higher for wider gamuts), and Hue (0–360°). Unlike HSL's lightness, OKLCH's L channel tracks actual perceived brightness consistently across hues, which is why it's the right tool for programmatically generated ramps and gradients rather than one-off hand-picked colors. The full math and worked comparison against HSL is in what OKLCH is and why it matters. oklab() is the same underlying model in its rectangular (a/b) form rather than cylindrical (chroma/hue) form — useful for direct interpolation math, less common to write by hand in a stylesheet.
lab() and lch() — the CIE 1976 model, also now native
.legacy-brand-match {
background: lab(53% 40 30);
}Lab and its cylindrical form LCH predate OKLCH by decades and were already the standard in print and photography color management long before CSS adopted them — this site's color name finder leans on that same Lab space for its Delta-E nearest-match distance, precisely because it's the older, well-proven perceptual model. Lab is a genuine improvement over RGB/HSL for that kind of work, though it carries known hue-linearity issues OKLCH was specifically built to fix. If you're matching an existing brand spec that was defined in Lab (common when a brand system originated in print or in Adobe tools), lab()/lch() let you paste those values into CSS directly instead of converting to sRGB and losing precision along the way.
color() — explicit wide-gamut and colorspace control
.wide-gamut-accent {
background: color(display-p3 1 0.2 0.3);
}The color() function lets you specify a color explicitly in a named color space — srgb, display-p3, rec2020, xyz, and others — rather than always implicitly assuming sRGB. This matters because modern displays, especially phones and recent monitors, commonly support Display P3, a gamut roughly 25% larger than sRGB, particularly in the red and green range. A color authored in color(display-p3 ...) can be more saturated than anything representable in sRGB hex, and will render more vividly on a P3-capable screen while gracefully falling back to the nearest in-gamut sRGB approximation on older displays. See understanding color gamuts for when this is actually worth reaching for versus over-engineering a project that doesn't need it.
color-mix() — mixing two colors, natively, in a chosen space
.hover-state {
background: color-mix(in oklch, var(--brand) 80%, white);
}Before color-mix(), producing a "20% lighter" or "blended with white" variant of a brand color required either a preprocessor function (Sass's mix(), still widely used) or a JavaScript-computed value baked into a custom property at build time. color-mix() does this natively at the CSS layer, and — critically — lets you specify the interpolation space. Mixing in oklch versus the default in srgb (technically srgb-linear semantics differ subtly too) can produce visibly different results for the same percentage split, for the identical reason gradient interpolation space matters: RGB-space mixing between distant hues tends to pass through a duller, grayer midpoint than OKLCH-space mixing does. This is a genuinely practical replacement for a lot of what a Sass color function used to do, now runtime-computable and themeable via custom properties without a build step.
Relative color syntax — deriving a new color from an existing one, in CSS
.darker-variant {
background: oklch(from var(--brand) calc(l - 0.15) c h);
}Relative color syntax lets you take an existing color (a literal, a variable, any valid color value) and produce a new one by referencing and modifying its individual channels — here, taking the brand color's OKLCH representation and subtracting 0.15 from lightness while leaving chroma and hue untouched. This is the direct CSS-native answer to what a lot of teams previously reached for a Sass darken()/lighten() function to do, and it works across any of the supported color functions (rgb(from ...), hsl(from ...), oklch(from ...)), not just OKLCH.
Gradient interpolation space as a first-class argument
.smooth-gradient {
background: linear-gradient(in oklch, #ff6347, #4169e1);
}Every gradient function (linear-gradient, radial-gradient, conic-gradient) now accepts an explicit color-interpolation-method argument, which is the fix for the muddy-midpoint problem covered in more detail in gradient design trends — a straight RGB-space interpolation between two hues on opposite sides of the wheel tends to desaturate through gray in the middle, and specifying in oklch (or in hsl, taking the shorter or longer hue arc explicitly) fixes it without any JavaScript. Build gradients visually with the gradient generator and export the resulting CSS, interpolation method included, with gradient-to-code.
What this means practically for a stylesheet in 2026
Hex and rgb()/hsl() remain completely valid and aren't being deprecated — they're simply no longer the only options, and for specific jobs (evenly-stepped ramps, gradient interpolation, deriving a variant from an existing brand token without recomputing it externally) the newer functions solve real problems the older ones couldn't. A pragmatic approach: keep hex or HSL for static, hand-picked one-off values where the difference is imperceptible, and reach for oklch(), color-mix(), or relative color syntax specifically where you're deriving, blending, or programmatically generating color rather than picking it directly. See CSS variables for theming for how these functions combine with custom properties to build a genuinely runtime-themeable color system, and the CSS variable exporter for generating a starter token file.
A worked color-mix() example with real computed stops
It helps to see \color-mix()\ produce an actual ramp rather than a single blended value. Starting from a brand color of \oklch(55% 0.2 260)\ (a mid-toned blue-violet), mixing progressively more white in \oklch\ space at 20%, 40%, 60%, and 80% gives four genuinely usable lighter steps without hand-picking any of them:
\\\css :root { --brand: oklch(55% 0.2 260); --brand-80: color-mix(in oklch, var(--brand) 80%, white); /* lightest step still clearly the brand hue */ --brand-60: color-mix(in oklch, var(--brand) 60%, white); --brand-40: color-mix(in oklch, var(--brand) 40%, white); --brand-20: color-mix(in oklch, var(--brand) 20%, white); /* nearly white, faint brand tint */ } \\\
Because the mix runs in OKLCH rather than the \in srgb\ default, each of the four steps holds onto a consistent perceived hue as it lightens — mixing the identical percentages \in srgb\ instead produces steps that shift subtly warmer or cooler partway through the ramp, an artifact of RGB's non-uniform channel blending rather than anything intentional in the original brand color.
light-dark() for a one-line theme switch
A newer, narrower-purpose function worth knowing alongside \color-mix()\: \light-dark()\ takes two color arguments and picks between them based on the page's resolved color scheme, without a separate \[data-theme]\ selector block at all:
\\\css :root { color-scheme: light dark; --surface: light-dark(#ffffff, #121316); --text: light-dark(#14151a, #f3f4f6); } \\\
This is a genuinely different mechanism from the \[data-theme="dark"]\ custom-property override pattern covered in CSS variables for theming — \light-dark()\ resolves based on the \color-scheme\ property and the user's \prefers-color-scheme\ setting directly, which is convenient for a small number of simple tokens but doesn't give you the same explicit, user-overridable toggle a stored \data-theme\ attribute does. A reasonable pattern for a real product: \light-dark()\ for content that only ever needs to track system preference, the \data-theme\ attribute approach for anything a user needs to explicitly override.
Shipping newer functions safely with @supports
None of the syntax above needs to gate an entire stylesheet behind a feature check — CSS silently ignores a declaration it can't parse, so a browser that doesn't understand \oklch()\ simply skips that one declaration rather than breaking the rule. Where \@supports\ earns its keep is layering an explicit, guaranteed-safe fallback ahead of the newer value:
\\\`css .button { background: #3b82f6; /* fallback for engines with no oklch() support at all */ }
@supports (background: oklch(55% 0.2 260)) { .button { background: oklch(55% 0.2 260); } } \\\`
Because the fallback declaration comes first and the \@supports\-gated one comes second, a fully supporting browser simply overrides the fallback with the more precise value, while an older engine keeps the plain hex and never sees the \oklch()\ line at all. This pattern is worth reaching for specifically on visually load-bearing brand colors, less so on incidental UI accents where a very close approximation is in practice indistinguishable to a user.