Whether itās the rgb()
color function or the hsl()
color function, there is now support for a relative syntax using the from keyword. This allows you to set similar or complementary colors based on the existing color value.
It is especially useful for implementing interactive effects such as hover states.
The Essence of the relative syntax Is Breaking down colors.
Letās first learn the syntax. This syntax may look fancy, but itās actually quite simple and easy to pick up.
For example, to set the text color to red:
p {
color: rgb(from red r g b / alpha);
}
The real-time rendering effect is as follows:
If youāre using the Chrome browser or Safari version 18 or above, youāll see that the text above (including the outer dotted text) will all be red.
The r, g, b, and alpha values are actually the decomposed components of the red color, with calculated values of 255, 0, 0, and 1 (or 100%).
Therefore, we can then apply these decomposed values to set the corresponding color.
Set directly
The r, g, b, and alpha values can be directly replaced with numbers to change the computed color value.
For example, to set a red color with 50% opacity, we can do this:
p {
color: rgb(from red r g b / .5);
}
The real-time rendering effect is as follows:
This method is easier than using color-mix()
, but color-mix()
has been supported for longer and has better compatibility.
For example, the RGB value for purple is rgb(255, 0, 255)
, so by replacing the b in the code above with 255, we can get purple.
p {
color: rgb(from red r g 255 / alpha);
}
Note that in actual development, we wouldnāt use the above method. This is just to demonstrate the syntax.
Using calc() in Practice
In production, we generally use the calc()
function to perform relative calculations on color values. The reason is simple: if you already know the exact color value, why bother using relative syntax? It would be redundant.
Syntax example: Halving the red color.
canvas {
background-color: rgb(from red calc(r / 2) g b / alpha);
/* equal rgb(128,0,0) */
}
At this point, itās a dark red background.
The example above isnāt commonly used; itās just to demonstrate how calc() works.
Text automatically adapting to the background color
Now, letās get to the main topic.
Actually, I use the concept of text automatically adapting to the background color many years ago. However, back then it required using r, g, and b as separate variables.
Now, with the relative syntax for color values, our implementation is much simpler.
Letās assume thereās a button with the class name btn:
<button class="btn">I'm a button</button>
.btn {
font-size: 150%;
padding: .5em 2em;
--bgcolor: var(--color, #2c87ff);
background-color: var(--bgcolor);
color: hsl(from var(--bgcolor) h s calc((l - 60) * -999999));
border: .2em solid;
border-color: hsl(from var(--bgcolor) h s calc(l - 20 * clamp(-1, calc(l - 50), 1)));
}
The real-time rendering effect is as follows:
By changing the value of the color input field on the demo page, you can see that as the color changes, the button text automatically adapts to either white or black. The border color, on the other hand, uses a dark color on a light background and a light color on a dark background. This is a smart implementation, and the effect is shown in the screen recording below:
Hover, click, and other color changes in adjacent states
When hovering over a text link, the color will be appropriately highlighted.
In the past, we would choose two different color values to handle this.
Now, in addition to using color-mix()
(mixing with white), we can also use the relative color syntax here. However, we can no longer use the rgb()
function; instead, we need to use the hsl()
function. This is because the color change for such interactions is typically an adjustment of brightness or saturation, and the hsl()
function is perfect for this purpose.
<a href class="link">I'm a link</a>
.link {
--color: #2a80eb;
color: var(--color);
}
.link:hover {
color: hsl(from var(--color) h s calc(l + 10));
}
Similarly, when clicking the button, the background color will darken appropriately.
In addition to using box-shadow for an inner shadow or the filter property with the brightness filter, we can also try using relative color values.
Usage example:
<button class="btn">I'm a button</button>
.btn {
--color: #2a80eb;
background-color: var(--color);
color: #fff;
border: 0;
}
.btn:active {
background-color:hsl(from var(--color) h s calc(l - 10));
}
There are so many new features that itās overwhelming.
The relative color syntax in CSS is now supported by all modern browsers. See the screenshot below:
These features have only been supported recently.
Based on past experience, it will likely be another two years before we can use them in projects like middle-platform systems.
Many new features overlap with each other, such as CSS nesting and the CSS @scope syntax, CSS relative colors and the color-mix()
function, etc.
The problem is that browser support for these features is inconsistent, making it difficult to apply them in production environments in the short term. As a result, hardly anyone in the community is discussing them.
The current JavaScript capabilities are sufficient to handle all interactive scenarios.
These ānice-to-haveā features lack urgency and necessity, which makes it hard for people to take them seriously. In fact, it would be strange if they did.
It feels like the people setting the standards are pushing out new features for their ākpiā, constantly adding more and more. Whether this trend of āmore is betterā is a good thing or a mistake will only be answered by time.