Modern Color Functions
Recent CSS has added an entire family of color functions that reduce the need for hand-maintained color palettes and preprocessor color utilities. This page rounds up three of the most useful — a quick recap of
color-mix(), relative color syntax for deriving variants of an existing color, and light-dark() for concise light/dark theming — and finishes with a summary table of everything covered across this series.color-mix() — a quick recap
color-mix() blends two colors by a percentage, computed in a chosen color space. It's covered in full at color-mix() & relative colors — the example below shows the classic use case of generating a tint (mixed toward white) and a shade (mixed toward black) from a single brand color, which used to require either a design tool or a Sass color function.CSS
:root {
--brand: #0066cc;
}
.btn {
background: var(--brand);
}
.btn:hover {
/* Tint — 15% white mixed in */
background: color-mix(in srgb, var(--brand) 85%, white);
}
.btn:active {
/* Shade — 20% black mixed in */
background: color-mix(in srgb, var(--brand) 80%, black);
}Relative color syntax
Relative color syntax lets you take an existing color — a literal, a custom property, anything — and produce a new color by keeping some channels and modifying others. The general shape is
colorfunction(from <origin-color> ch1 ch2 ch3 / alpha), where any channel can either be passed through unchanged, replaced with a literal, or computed with calc().CSS
:root {
--base: rgb(0 102 204);
}
/* Same color, but 50% transparent — only alpha changes */
.overlay {
background: rgb(from var(--base) r g b / 0.5);
}
/* Derive a lighter variant by working in oklch and bumping lightness */
.badge {
--base-oklch: oklch(55% 0.19 258);
background: oklch(from var(--base-oklch) calc(l + 0.2) c h);
}
/* Derive a desaturated, slightly rotated-hue variant for a "muted" style */
.muted {
background: oklch(from var(--base-oklch) l calc(c * 0.4) calc(h + 15));
}This is genuinely powerful for a single-source-of-truth palette
Instead of hand-picking and hard-coding a hover color, an active color, a muted color and a border color for every brand hue in your palette, relative color syntax lets you declare one base color per hue and derive every variant from it with a formula. Change the base color once, and every derived variant recalculates automatically.
light-dark() — one declaration, two theme values
light-dark() takes a light-mode value and a dark-mode value and picks between them automatically, based on the page's color-scheme. It's a much more concise alternative to writing a separate @media (prefers-color-scheme: dark) block for every color declaration — see Dark Mode (prefers-color-scheme) for the fuller theming picture, including how color-scheme needs to be set for light-dark() to know which mode is active.CSS
:root {
color-scheme: light dark; /* opt in to both modes */
}
body {
/* light value, dark value — no separate @media block needed */
background: light-dark(#ffffff, #121212);
color: light-dark(#111111, #eeeeee);
}
.card {
border-color: light-dark(#e0e0e0, #333333);
}Summary of modern color functions
Function | What it does |
|---|---|
| Perceptually uniform color spaces — see oklch() & Modern Color Spaces |
| Blends two colors by a percentage in a chosen color space |
relative color syntax | Derives a new color by modifying specific channels of an existing color |
| Picks between a light-mode and dark-mode value based on |
| Picks the most readable color from a list against a background — support varies, check before relying on it |
Next
Position tooltips and popovers relative to an anchor, without JavaScript measuring: [Anchor Positioning](/css/anchor-positioning).