CSS relative color syntax — color(from baseColor channel1 channel2 channel3) — lets you derive new colors from existing ones by manipulating individual channels. It replaces complex color-mix() workarounds and makes it trivial to build tints, shades, and transparent variants from a single design token.
Problem: CSS colour palettes require maintaining dozens of manually derived tints, shades, and transparent variants — changing the brand colour means updating every hex value across the stylesheet independently.
Solution: Use the CSS Relative Color Syntax (oklch(from var(--color) calc(l + 0.1) c h)) to derive tints and shades from a single custom property. Combine with CSS color-mix() for blends. All variants update automatically when the base variable changes, eliminating manual palette maintenance.
The examples below lighten and darken a brand color using relative oklch syntax, generate hover states automatically, and create a full CSS palette from a single custom property.
/* Relative color syntax: color(from / ) */
:root {
--brand: oklch(55% 0.18 250); /* single source of truth */
}
/* Lighten: increase lightness channel by 15% */
.btn-light {
background: oklch(from var(--brand) calc(l + 0.15) c h);
}
/* Darken: decrease lightness */
.btn-dark {
background: oklch(from var(--brand) calc(l - 0.15) c h);
}
/* Desaturate: reduce chroma */
.btn-muted {
background: oklch(from var(--brand) l calc(c * 0.4) h);
}
/* Complementary color: rotate hue by 180° */
.btn-complementary {
background: oklch(from var(--brand) l c calc(h + 180));
}
/* Transparent variant: set alpha channel */
.overlay {
background: oklch(from var(--brand) l c h / 0.2);
}
/* Works with any color space — hsl example */
.btn-hsl-light {
background: hsl(from var(--brand) h s calc(l + 20%));
}
/* Generate automatic hover state without JavaScript */
.btn {
background: var(--brand);
transition: background 0.2s;
}
.btn:hover {
background: oklch(from var(--brand) calc(l + 0.08) c h);
}
Build a complete palette from one token:
/* Auto-generate a 9-step palette from --brand */
:root {
--brand: oklch(55% 0.18 250);
--color-50: oklch(from var(--brand) 0.97 calc(c * 0.1) h);
--color-100: oklch(from var(--brand) 0.93 calc(c * 0.2) h);
--color-200: oklch(from var(--brand) 0.85 calc(c * 0.4) h);
--color-300: oklch(from var(--brand) 0.76 calc(c * 0.6) h);
--color-400: oklch(from var(--brand) 0.67 calc(c * 0.8) h);
--color-500: var(--brand); /* base */
--color-600: oklch(from var(--brand) calc(l - 0.08) c h);
--color-700: oklch(from var(--brand) calc(l - 0.16) c h);
--color-800: oklch(from var(--brand) calc(l - 0.24) c h);
--color-900: oklch(from var(--brand) 0.20 calc(c * 0.5) h);
}
/* Theme switching — change only the token, all palette updates automatically */
[data-theme="green"] { --brand: oklch(55% 0.17 145); }
[data-theme="red"] { --brand: oklch(52% 0.22 25); }
NOTE: Relative color syntax is supported in Chrome 119+, Safari 16.4+, and Firefox 128+. Use @supports (color: oklch(from red l c h)) to progressively enhance — fall back to hardcoded palette values for older browsers. This feature eliminates most uses of Sass color functions like lighten() and darken() in modern CSS.