Back

Relative Color Syntax in CSS Explained

Relative Color Syntax in CSS Explained

If you’ve ever defined a dozen custom properties just to create a semi-transparent version of your brand color, you already understand the problem CSS relative color syntax solves. This feature lets you derive new colors directly from existing ones inside any modern CSS color function — no preprocessors, no JavaScript, no extra variables.

Key Takeaways

  • CSS relative color syntax uses the from keyword to derive new colors from an existing origin color by exposing its individual channels as modifiable variables.
  • It works across rgb(), hsl(), hwb(), lab(), lch(), oklab(), and oklch().
  • OKLCH is the preferred color space for manipulation because its lightness channel is perceptually uniform, producing visually consistent results across hues.
  • Relative colors integrate naturally into token-based design systems, letting you define one base color and derive every variant from it without preprocessors or build steps.

What Is CSS Relative Color Syntax?

CSS relative colors, defined in the CSS Color Level 5 specification, let you take an existing color — called the origin color — and use its individual channels as variables to construct a new color. The key addition is the from keyword.

Here is the basic structure:

color-function(from <origin-color> channel1 channel2 channel3)

When the browser encounters from, it converts the origin color into the target color space, exposes each channel as a named variable, and lets you pass those variables — modified or unchanged — as the output values.

This works across modern CSS color functions such as rgb(), hsl(), hwb(), lab(), lch(), oklab(), and oklch().

How the CSS from Color Syntax Works

Take this simple example:

/* Origin color: green */
/* Channels exposed: r=0, g=128, b=0 */
color: rgb(from green r g b); /* outputs rgb(0 128 0) */

The output is identical to the input here, but the real power is that you can modify individual channels using calc():

/* Rotate the hue by 180 degrees to get the complement */
background: hsl(from blue calc(h + 180) s l);

/* Lighten by multiplying the lightness channel */
background: oklch(from blue calc(l * 1.25) c h);

/* Reduce opacity without a separate variable */
background: rgb(from lime r g b / 50%);

Channel values resolve to plain numbers inside the function, so calc() arithmetic works cleanly without unit mismatches.

Why OKLCH Relative Colors Are Worth Using

Not all color spaces are equal for manipulation. HSL is familiar, but its lightness channel is not perceptually uniform — adjusting l by the same amount produces visually inconsistent results across different hues.

OKLCH solves this. Its lightness channel is perceptually uniform, meaning a +0.1 shift looks like the same visual step regardless of hue. That makes it a strong choice for generating tints, shades, and accessible contrast pairs.

If you’re unfamiliar with perceptual color spaces, the OKLab/OKLCH color model introduced by Björn Ottosson explains the reasoning behind these newer color spaces.

/* Darken by 25% */
background: oklch(from var(--color-primary) calc(l * 0.75) c h);

/* Generate a high-contrast text color */
color: oklch(from var(--color-primary) calc(l + 0.6) c h);

Note that the lightness channel in OKLCH ranges from 0 to 1, so a calc(l + 0.6) shift is substantial. Values that exceed the valid range are clamped by the browser automatically.

Relative Colors vs. color-mix()

These two CSS color features serve different purposes. color-mix() blends two colors together at a specified ratio. CSS relative colors manipulate a single origin color’s channels directly. If you need a hover state, a muted variant, or an opacity-adjusted token, relative syntax is the right tool. If you need to interpolate between two distinct colors, reach for color-mix().

Fitting Into a Token-Based Design System

This is where CSS relative colors genuinely shine. Define one base token, then derive every variant from it:

:root {
  --color-primary: #3b82f6;

  --color-primary-hover:    oklch(from var(--color-primary) calc(l * 0.9) c h);
  --color-primary-active:   oklch(from var(--color-primary) calc(l * 0.8) c h);
  --color-primary-disabled: oklch(from var(--color-primary) l c h / 0.5);
  --color-on-primary:       oklch(from var(--color-primary) calc(l + 0.6) c h);
}

Change --color-primary and every derived token updates automatically. No Sass functions, no build step.

Browser Support

Relative color syntax is supported in modern versions of Chromium, Firefox, and Safari. Use @supports to gate it safely:

@supports (color: rgb(from white r g b)) {
  /* relative color syntax is available */
}

For browsers that lack support, provide a fallback color before the relative color declaration. The cascade ensures older browsers use the static value while modern browsers override it:

.button {
  background: #3b82f6;
  background: oklch(from var(--color-primary) calc(l * 0.9) c h);
}

Conclusion

CSS relative color syntax replaces a whole category of preprocessor logic with a single, readable pattern. Pick OKLCH for perceptually consistent lightness adjustments, use hsl() when you need direct saturation control, and wire everything to a custom property so your entire palette stays in sync from one source token.

FAQs

Yes. You can pass any custom property as the origin color using var(). For example, oklch(from var(--brand-color) calc(l * 0.8) c h) works as expected. The browser resolves the variable first, then converts the resulting color into the target color space and exposes its channels.

The browser clamps out-of-range values automatically. For instance, if a calc() expression pushes OKLCH lightness above 1 or below 0, the browser clips it to the valid range. This means you do not need to manually guard against overflow in most cases.

The performance impact is negligible for typical use. The browser resolves relative colors at computed-value time, similar to how it resolves calc() in other properties. You would need thousands of relative color declarations on a single page before any measurable difference appeared.

Relative color functions can be chained if the result of one transformation resolves to a valid color value. In practice, developers often assign the result of a relative color calculation to a custom property and use that as the origin color for another transformation, allowing multiple steps of color manipulation while keeping the code readable.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay