Where color-mix() blends two colors together, Relative Color Syntax takes a more surgical approach: reach into one existing color and adjust exactly the channel you need, leaving everything else identical.
1Referencing And Overriding Individual Channels
Relative Color Syntax is available on rgb(), hsl(), hwb(), lab(), lch(), oklab(), and oklch(). The pattern is always function(from <source-color> <channel1> <channel2> <channel3> [/ alpha]), where each channel slot can either reference the source's own value by its channel name, or be overridden with a literal.
hsl(from var(--color-brand) h s 30%) keeps the brand color's hue and saturation completely intact, while forcing lightness to exactly 30% — producing a darker variant that's unambiguously still 'the brand color', just darker, rather than an unrelated dark color that happens to look similar.
2True Relative Math With calc()
Overriding a channel with a fixed literal (like 30%) still requires knowing the right target value ahead of time. calc() inside a channel slot goes further, letting the new value be computed *relative to* the source's original value: calc(l - 15%) means 'fifteen percentage points darker than whatever this color's lightness already was', which produces a consistent, proportional darkening across any input color, including ones set dynamically via a custom property.
This is what makes Relative Color Syntax and color-mix() complementary rather than redundant: color-mix() answers 'blend these two colors together'; Relative Color Syntax answers 'adjust exactly this one channel of this one color, possibly relative to its current value'.
3A Useful Side Effect: Native Format Conversion
Because a relative color expression always has to compute the source color's channels in the target function's color space to evaluate at all, passing every channel straight through unmodified (hsl(from #FF0099 h s l)) still forces that computation — meaning the browser has effectively converted the original hex color into its HSL equivalent as a byproduct.
This isn't the primary intended use of the feature, but it's a genuinely handy trick for debugging or generating equivalent color values across formats directly in CSS or devtools, without reaching for an external color converter tool.
4Step-by-Step Breakdown
Manipulating A Color's Own Channels. color-mix() blends two colors together. Relative Color Syntax does something more surgical: it lets you take one existing color and directly manipulate a single channel — lower just the lightness, adjust just the alpha — while leaving everything else about it untouched.
The from Keyword: Referencing An Existing Color. Relative color syntax starts with a function like hsl(), rgb(), or oklch(), followed by from and a source color. Inside, you can reference that source's individual channels by name (h, s, l for HSL; r, g, b for RGB) to build a new color derived from it.
The from Keyword. In hsl(from var(--color-brand) h s 30%), what does this expression produce?
- →The exact same color as --color-brand, unchanged
- →The same hue and saturation as --color-brand, but with lightness forced to 30%
- →This is invalid CSS syntax
Channel Math: calc() Inside Color Channels. Referenced channels aren't just static values you can keep or override — you can perform math on them directly with calc(), letting you express 'take this color and make it 20% darker' as a genuine relative calculation instead of a hardcoded target value.
Channel Calculations. What does calc(l - 15%) do inside a relative color expression?
- →Sets lightness to a fixed 15%, ignoring the source color
- →Subtracts 15 percentage points from the source color's original lightness value
- →calc() cannot be used inside a color function at all
Cross-Function Conversion As A Side Effect. Because relative color syntax works across rgb(), hsl(), oklch(), and other functions, writing hsl(from #FF0099 h s l) is also, incidentally, a native way to convert a hex color into its HSL representation — no separate conversion tool required.
Conversion As A Side Effect. What does hsl(from #FF0099 h s l), with every channel passed through unmodified, effectively accomplish?
- →Nothing useful — it's functionally identical to just writing #FF0099
- →It converts the hex color into its equivalent HSL representation
- →It throws a CSS parsing error since no channel is being changed
Relative Color Mastery. You can now derive a new color by directly manipulating one existing color's individual channels, apply genuine relative math with calc() inside those channels, and even use the syntax as a native color-format converter — all without a preprocessor or JavaScript color library.
Derive A Color From Another Color. rgb(from ...) reads a color's own channels, letting you adjust just one without hardcoding the whole value.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Relative Lightness Adjustments Should Be Verified Against Contrast Requirements, Not Assumed Safe
A calc(l - 15%) darkening looks intuitively 'more accessible' for text-on-background contrast, but the actual resulting contrast ratio still needs verification — relative math doesn't guarantee a specific WCAG compliance outcome.
2Preserving Hue While Adjusting Lightness Helps Maintain Brand Recognizability For Low-Vision Users
Because Relative Color Syntax can isolate lightness changes while keeping hue and saturation fixed, it's well suited for generating accessible-contrast variants of a brand color that remain visually identifiable as the same color family.
SEO Implications
- 1
Deriving Palette Variants Natively Reduces Reliance On Build-Time Color Preprocessing
Teams that previously used a Sass color function pipeline purely for generating tints, shades, and format conversions may be able to simplify their build tooling, incrementally speeding up CI.
- 2
Fewer Hardcoded Color Values Reduces Total Declared CSS And Improves Long-Term Maintainability
A smaller, more systematically-derived color palette tends to compress better and requires fewer individual declarations across a large stylesheet.
Best Practices
Use calc() Inside Channels For Any Adjustment That Should Scale Proportionally With The Input Color
A relative adjustment (like darkening by a fixed percentage) behaves correctly across any dynamically-set base color, whereas a hardcoded target value only looks right for the specific color it was tuned against.
Prefer Relative Color Syntax Over color-mix() When You're Only Adjusting One Channel
It's more explicit about intent — 'darken this color's lightness' reads more clearly than 'mix this color with black', even though the latter can sometimes achieve a visually similar result.
Frequent Bugs
A derived hover color looks correct for the default brand color but wrong after a theme or brand-color change.
The hover state used a hardcoded literal channel override instead of a calc()-based relative adjustment; switch to calc(l - 15%) style math so it scales correctly with any base color.
A relative color expression silently fails to apply in an older browser.
Relative Color Syntax requires relatively recent browser support; verify your target browser matrix or provide a static fallback color declared before the relative expression.
Real-World Examples
A Systematic Hover/Active Scale From One Token
A button component deriving proportionally darker hover and active states purely through relative lightness math on a single brand token.
:root { --color-brand: #FF0099; }
.btn { background: var(--color-brand); }
.btn:hover { background: hsl(from var(--color-brand) h s calc(l - 10%)); }
.btn:active { background: hsl(from var(--color-brand) h s calc(l - 20%)); }