1. Introduction
This section is not normative.
Web developers, design tools and design system developers often use color functions to assist in scaling the design of their component color relations. With the increasing usage of design systems that support multiple platforms and multiple user preferences, like the increased capability of Dark Mode in UI, this becomes even more useful to not need to manually set color, and to instead have a single source from which schemes are calculated.
Currently Sass, calc() on HSL values, or PostCSS is used to do this. Preprocessors are unable to work on dynamically adjusted colors, all current solutions are restricted to the sRGB gamut and to the perceptual limitations of HSL (colors are bunched up in the color wheel, and two colors with visually different lightness, like yellow and blue, can have the same HSL lightness).
This module adds three functions: color-mix, color-contrast, and a way to modify colors.
The perceptually uniform ``lch()`` colorspace is used for mixing by default, as this has no gamut restrictions and colors are evenly distributed. However, other colorspaces can be specified, including ``hsl()`` or ``srgb`` if desired.
2. Mixing colors: the color-mix function
This function takes two <color> specifications and returns the result of mixing them, in a given colorspace, by a specified amount.
Unless otherwise specified, the mixing is done in the lch() colorspace.
Multiple color functions can be specified.
color-mix() = color-mix( <color> <color> [ <number> | <percentage> | [ <color-function> <colorspace>? ]?] )
mix-color(red, white, lightness(50%));
The calculation is as follows:
-
sRGB red (#F00) is lch(54.2917 106.8390 40.8526)
-
sRGB white (#FFF) is lch(100 0 0)
-
mix lightness is 54.2917 * 0.5 + 100 * 0.5 = 77.14585
-
mixed result is lch(77.14585 81.95 37.192)
-
which results in a lighter red, like a Salmon color
mix-color(red, yellow, lightness(30%));
The calculation is as follows:
-
sRGB red (#F00) is lch(54.2917 106.8390 40.8526)
-
sRGB yellow (#FF0) is lch(97.6071 94.7077 99.5746)
-
mix lightness is 54.2917 * 0.3 + 97.6071 * 0.7 = 84.6125
-
mixed result is lch(84.6125 106.8390 40.8526)
-
which is a very light red (and outside the gamut of sRGB: rgb(140.4967% 51.2654% 32.6891%))
Instead of a list of color functions, a plain number or percentage can be specified, which applies to all color channels.
Note: interpolating on hue and chroma keeps the intermediate colors as saturated as the endpoint colors.
mix-color(red, yellow, 65%);
The calculation is as follows:
-
sRGB red (#F00) is lch(54.2917 106.8390 40.8526)
-
sRGB yellow (#FF0) is lch(97.6071 94.7077 99.5746)
-
mix lightness is 54.2917 * 0.65 + 97.6071 * 0.35 = 69.4521
-
mix chroma is 106.83 * 0.65 + 94.7077 * 0.35 = 102.5872
-
mix hue is 40.8526 * 0.65 + 99.5746 * 0.35 = 61.4053
-
mixed result is lch(69.4521 102.5872 61.4053)
-
which is a red-orange: rgb(75.3600% 65.6304% 16.9796%)
3. Selecting the most contrasting color: the color-contrast() function
This function takes, firstly, a single color (typically a background, but not necessarily), and then second, a list of colors; it selects from that list the color with highest luminance contrast to the single color.
color-contrast(wheat tan, sienna, var(--myAccent), #d2691e)
The calculation is as follows:
-
wheat (#f5deb3), the background, has relative luminance 0.749
-
tan (#d2b48c) has relative luminance 0.482 and contrast ratio 1.501
-
sienna (#a0522d) has relative luminance 0.137 and contrast ratio 4.273
-
suppose myAccent has the value #b22222
-
#b22222 has relative luminance 0.107 and contrast ratio 5.081
-
#d2691e has relative luminance 0.305 and contrast ratio 2.249
-
The highest contrast ratio is 5.081 so var(--myAccent) wins
4. Modifying colors
Note: There are currently two proposals for modifying colors: color-adjust and Relative color syntax.
there are two proposals for color modification (proposal 1, proposal 2). The CSS WG expects that the best aspects of each will be chosen to produce a single eventual solution. <https://github.com/w3c/csswg-drafts/issues/3187>
4.1. Adjusting colors: the color-adjust function
This function takes one <color> specification and returns the result of adjusting that color, in a given colorspace, by a specified transform function.
Unless otherwise specified, the adjustment is done in the lch() colorspace.
Multiple color functions can be specified.
color-adjust() = color-adjust( <color> [ <color-function> <colorspace>? ]?] )
adjust-color(peru, lightness(-20%));
The calculation is as follows:
-
peru (#CD853F) is lch(62.2532% 54.0114 63.6769)
-
adjusted lightness is 62.2532% - 20% = 42.2532%
-
adjusted result is lch(42.2532% 54.0114 63.6769)
-
which is rgb(57.58%, 32.47%, 3.82%)
4.2. Relative color syntax
Besides specifying absolute coordinates, all color functions can also be used with a *relative syntax* to produce colors in the function’s target color space, based on an existing color (henceforth referred to as "origin color"). This syntax consists of the keyword from, a <color> value, and optionally numerical coordinates specific to the color function. To allow calculations on the original color’s coordinates, there are single-letter keywords for each coordinate and `alpha` that corresponds to the color’s alpha. If no coordinates are specified, the function merely converts the origin color to the target function’s color space.
The following sections outline the relative color syntax for each color function.
A future version of this specification may define a relative syntax for color() as well.
4.2.1. Relative RGB colors
The grammar of the rgb() function is extended as follows:
rgb() = rgb([from <color>]? <percentage>{3} [ / <alpha-value> ]? ) | rgb([from <color>]? <number>{3} [ / <alpha-value> ]? ) <alpha-value> = <number> | <percentage>
When an origin color is present, the following keywords can also be used in this function (provided the end result conforms to the expected type for the parameter) and correspond to:
-
r is a <percentage> that corresponds to the origin color’s red channel after its conversion to sRGB
-
g is a <percentage> that corresponds to the origin color’s green channel after its conversion to sRGB
-
b is a <percentage> that corresponds to the origin color’s blue channel after its conversion to sRGB
-
alpha is a <percentage> that corresponds to the origin color’s alpha transparency
4.2.2. Relative HSL colors
The grammar of the hsl() function is extended as follows:
hsl() = hsl([from <color>]? <hue> <percentage> <percentage> [ / <alpha-value> ]? ) <hue> = <number> | <angle>
When an origin color is present, the following keywords can also be used in this function (provided the end result conforms to the expected type for the parameter) and correspond to:
-
h is a <number> that corresponds to the origin color’s HSL hue after its conversion to sRGB, normalized to a [0, 360) range.
-
s is a <percentage> that corresponds to the origin color’s HSL saturation after its conversion to sRGB
-
l is a <percentage> that corresponds to the origin color’s HSL lightness after its conversion to sRGB
-
alpha is a <percentage> that corresponds to the origin color’s alpha transparency
4.2.3. Relative HWB colors
The grammar of the hwb() function is extended as follows:
hwb() = hwb([from <color>]? <hue> <percentage> <percentage> [ / <alpha-value> ]? )
When an origin color is present, the following keywords can also be used in this function (provided the end result conforms to the expected type for the parameter) and correspond to:
-
h is a <number> that corresponds to the origin color’s HWB hue after its conversion to sRGB
-
w is a <percentage> that corresponds to the origin color’s HWB whiteness after its conversion to sRGB
-
b is a <percentage> that corresponds to the origin color’s HWB blackness after its conversion to sRGB
-
alpha is a <percentage> that corresponds to the origin color’s alpha transparency
4.2.4. Relative Lab colors
The grammar of the lab() function is extended as follows:
lab() = lab([from <color>]? <percentage> <number> <number> [ / <alpha-value> ]? )
When an origin color is present, the following keywords can also be used in this function (provided the end result conforms to the expected type for the parameter) and correspond to:
-
l is a <percentage> that corresponds to the origin color’s CIE Lightness
-
a is a <number> that corresponds to the origin color’s CIELab a axis
-
b is a <number> that corresponds to the origin color’s CIELab b axis
-
alpha is a <percentage> that corresponds to the origin color’s alpha transparency
-
lab(from var(--mycolor) l a b / 100%) sets the alpha of var(--mycolor) to 100% regardless of what it originally was.
-
lab(from var(--mycolor) l a b / calc(alpha * 0.8)) reduces the alpha of var(--mycolor) by 20% of its original value.
-
lab(from var(--mycolor) l a b / calc(alpha - 20%)) reduces the alpha of var(--mycolor) by 20% of 100%.
Note that all the adjustments are lossless in the sense that no gamut clipping occurs, since lab() encompasses all visible color. This is not true for the alpha adjustments in the sRGB based functions (such as’rgb()', 'hsl()', or 'hwb()'), which would also convert to sRGB in addition to adjusting the alpha transparency.
4.2.5. Relative LCH colors
The grammar of the lch() function is extended as follows:
lch() = lch([from <color>]? <percentage> <number> <hue> [ / <alpha-value> ]? )
When an origin color is present, the following keywords can also be used in this function (provided the end result conforms to the expected type for the parameter) and correspond to:
-
l is a <percentage> that corresponds to the origin color’s CIE Lightness
-
c is a <number> that corresponds to the origin color’s LCH chroma
-
h is a <number> that corresponds to the origin color’s LCH hue, normalized to a [0, 360) range.
-
alpha is a <percentage> that corresponds to the origin color’s alpha transparency
4.2.6. Relative grayscale colors
The grammar of the gray() function is extended as follows:
gray() = gray([from <color>]? <number> [ / <alpha-value> ]? )
When an origin color is present, the following keywords can also be used in this function (provided the end result conforms to the expected type for the parameter) and correspond to:
-
l is a <percentage> that corresponds to the origin color’s CIE Lightness
-
alpha is a <percentage> that corresponds to the origin color’s alpha transparency