Modern CSS Color Functions: How to Use Them
Learn when to use oklch(), color(), color-mix(), and relative color syntax, with practical examples, compatibility guidance, and troubleshooting.
Use oklch() when you want to adjust a color by lightness, chroma, or hue; use color() when you need to name a specific color space such as Display P3; use color-mix() to interpolate between colors; and use relative color syntax to derive a variant from an existing color. Choose the function for the task, check support for the exact syntax in your target browsers, and test the final colors for contrast.
These functions are part of modern CSS color syntax, not universal drop-in replacements for hex or RGB. A stylesheet can keep familiar fallback values for older targets while using newer functions where supported.
1. Choose a function by what you need to do
| Function or syntax | Use it for | What the channels mean |
|---|---|---|
oklch() |
Human-directed palette adjustments | Lightness, chroma, hue |
oklab() |
Perceptual colors in rectangular coordinates | Lightness and two opponent axes |
lab() / lch() |
CIE Lab definitions | Rectangular Lab axes or lightness, chroma, hue |
hwb() |
Hue, whiteness, and blackness notation | Hue, white, black |
color() |
Explicitly name a color space, including wide-gamut spaces | Coordinates depend on the named space |
color-mix() |
Interpolate two colors in a chosen space | Input colors and proportions |
| Relative color syntax | Derive channels from an existing color | Origin color plus output channels |
The CSS Color Module Level 4 defines hwb(), lab(), lch(), oklab(), oklch(), and color(), among other color features. Read the W3C CSS Color Module Level 4 specification.
2. Use OKLCH for legible palette adjustments
oklch() expresses a color as lightness, chroma, and hue. It can make a design token easier to tune: lower lightness for a darker variant, lower chroma for a quieter one, or adjust hue while keeping the other channels visible in the notation. oklab() represents colors with lightness and two rectangular opponent axes instead.
:root {
--brand: oklch(62% 0.18 250);
--brand-dark: oklch(48% 0.18 250);
--brand-muted: oklch(62% 0.10 250);
}
.button {
color: white;
background-color: var(--brand);
}
.button:hover {
background-color: var(--brand-dark);
}
The values above demonstrate the channel roles; they are not a tested palette recommendation. A requested combination of lightness, chroma, and hue may be outside the displayable gamut for a target space, so inspect the rendered result on target devices and browsers.
3. Name a color space with color()
Use color(<space> ...) when you want the coordinate space to be explicit. The specification includes sRGB, linear-light sRGB, Display P3, linear-light Display P3, A98 RGB, ProPhoto RGB, BT.2020, and CIE XYZ spaces.
:root {
--orange-srgb: rgb(255 128 0);
--orange-p3: color(display-p3 1 0.5 0);
}
.swatch {
background: var(--orange-srgb);
}
@media (color-gamut: p3) {
.swatch {
background: var(--orange-p3);
}
}
The selected space and the display’s capabilities affect the result. The color-gamut media feature can help target a gamut, but it does not replace checking the exact syntax and behavior in supported browsers. See MDN’s reference for color().
4. Mix colors in a space suited to the transition
The general form is color-mix(in <space>, color percentage, color percentage). If no interpolation method is supplied, the default is Oklab. In a polar space such as Oklch, shorter-hue interpolation is the default.
:root {
--brand: oklch(62% 0.18 250);
--brand-soft: color-mix(in oklch, var(--brand) 25%, white);
--brand-mid: color-mix(in oklab, var(--brand) 50%, white);
--brand-light: color-mix(in srgb-linear, var(--brand) 35%, white);
}
Pick the interpolation space for the visual behavior you need. CIE XYZ or srgb-linear can suit simulations of physically mixed light intensities; Oklab or Lab can give perceptually even spacing; Oklch or LCH can help avoid a gray-looking midpoint by maintaining chroma. These are guidance, not a guarantee that one transition will look best in every design.
Percentages are normalized if they do not total 100%. If their sum is below 100%, the resulting color also receives an alpha multiplier corresponding to that sum. In polar spaces, the hue interpolation direction affects the path. MDN documents color-mix() as widely available across browsers since May 2023, while noting that some parts may vary; check support for the precise space and syntax you use. See MDN’s color-mix() reference.
5. Derive a color with relative color syntax
Relative color syntax starts with a color function, then from, an origin color, and output channel values. Each channel can copy an origin channel, use a replacement value, or use math to adjust a value. The origin can be a custom property, currentColor, a system color, or another relative color. If alpha is omitted, it inherits the origin color’s alpha.
:root {
--brand: oklch(62% 0.18 250);
--brand-muted: oklch(from var(--brand) l calc(c * 0.65) h);
--brand-brighter: oklch(from var(--brand) calc(l + 8%) c h);
--brand-transparent: oklch(from var(--brand) l c h / 50%);
}
Here the muted variant copies lightness and hue while reducing chroma. The brighter variant changes lightness and retains the other channels. These examples illustrate syntax; evaluate the resulting colors in your design. The relative form is documented for color(), hsl(), hwb(), lab(), lch(), oklab(), oklch(), and rgb(). See MDN’s guide to relative colors.
6. Build fallbacks and check browser support
Support varies by feature and syntax detail. MDN’s broad Baseline label for color-mix() does not mean that every interpolation space, relative syntax form, or adjacent color feature works in every browser version. Check compatibility for the exact function and options against your project’s browser targets.
For progressive enhancement, put a familiar fallback first and override it inside @supports when the target browser recognizes the newer declaration:
.card {
background-color: #e8e9ef;
background-color: rgb(232 233 239);
}
@supports (background-color: oklch(70% 0.1 250)) {
.card {
background-color: oklch(92% 0.02 250);
}
}
Use a support condition that tests the feature your component relies on. Validate the fallback too: it should remain usable and visually coherent when the enhanced declaration is ignored.
7. Check accessibility on the final colors
A color notation does not guarantee accessible contrast. A lighter OKLCH lightness value does not by itself tell you whether foreground text has enough contrast against its background. Evaluate the actual foreground and background pair, including hover, focus, disabled, and selected states, and do not communicate meaning through color alone.
The W3C specification says, “Authors should consider the Web Content Accessibility Guidelines (WCAG21) when using color in their documents.” Check your interface against the applicable accessibility requirements after the final colors are rendered.
8. Common problems and fixes
| Symptom | Likely cause | What to do |
|---|---|---|
| A declaration is ignored | The browser does not support that function or exact syntax. | Keep a valid fallback before it; check support for the precise feature and target browser. |
| A color looks different across displays | The display gamut differs, or the color uses a space the display cannot reproduce fully. | Inspect on target devices; use an appropriate fallback and test any gamut-specific rule. |
| A mixed color looks dull or takes an unexpected path | The interpolation space, proportions, or polar hue path do not match the desired effect. | Choose another interpolation space, adjust proportions, and inspect intermediate colors. |
| A relative color fails to parse | The target may not support relative syntax, or a channel expression is invalid. | Check the function-specific syntax and feature support; provide a standalone fallback color. |
| Text contrast is insufficient | Lightness or chroma adjustments were treated as a contrast check. | Assess the final foreground/background pair and all interactive states. |
| An intended variant is outside the expected gamut | The requested coordinates may not be reproducible in the output space or device. | Review the rendered color in target environments and adjust the channels or output space. |
9. Performance, reliability, and cost
CSS color functions let the browser interpret color values and interpolate colors; the research sources do not establish comparative performance figures. Avoid assuming that one notation is faster. Keep design tokens understandable, test transitions in the browsers you support, and measure your page if you suspect a rendering bottleneck.
Reliability comes from progressive enhancement and checking rendered behavior across target browsers and displays. A fallback reduces the impact when a newer declaration is unsupported. Wide-gamut values should be reviewed on relevant hardware because displays can differ in gamut.
These are browser-native CSS features and need no paid color-function service. For a page review or visual regression workflow, screenshot capture can help you inspect rendered states at chosen viewports. ScreenshotNeo is a website screenshot API and MCP server for developers; its capture options include device presets, viewport control, full-page capture, and custom CSS or JavaScript. See ScreenshotNeo and its API documentation.
10. Capture a rendered page with ScreenshotNeo
After you apply the CSS, capture the page at a viewport and browser state relevant to your review. The following cURL, Python, and Node.js examples request a WebP screenshot of a page; replace the URL with your own. The examples use the documented ScreenshotNeo endpoint and request pattern.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
For production use, keep the API key out of client-side code, handle non-success responses, and consult the ScreenshotNeo docs for parameters and response headers. For CSS review, set a viewport that exposes the layout state you want to inspect; a screenshot shows one captured state, so separately capture responsive and interaction states as needed.
Or skip the browser setup
ScreenshotNeo accepts cookie and consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server lets AI agents using Claude, Cursor, or another MCP client call take_screenshot, get_page_info, and capture_pdf. One thousand screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.
Frequently asked questions
Should I replace every hex value with OKLCH?
No. Choose notation based on the work you need to do and the browser support your project requires. Hex remains a useful simple representation; OKLCH is helpful when its lightness, chroma, and hue channels make a palette adjustment clearer.
Does color-mix() always produce a perceptually uniform gradient?
No. Its result depends on the interpolation space, percentages, and—for polar spaces—the hue path. Inspect the transition in context.
Does using a wide-gamut color guarantee that every visitor sees it?
No. The color space and the display capability both affect the result. Provide appropriate fallback behavior and check the target devices.
Does a lighter color necessarily improve text accessibility?
No. Accessibility depends on the final foreground and background together and on the interface state. Check the actual pair rather than inferring contrast from one channel.


