CSS Color Module Level 5

Editor’s Draft,

More details about this document
This version:
https://drafts.csswg.org/css-color-5/
Latest published version:
https://www.w3.org/TR/css-color-5/
Previous Versions:
Feedback:
CSSWG Issues Repository
Editors:
Chris Lilley (W3C)
Una Kravets (Google)
Lea Verou (Invited Expert)
Former Editor:
Adam Argyle (Google)
Suggest an Edit for this Spec:
GitHub Editor
Delta Spec:
yes
Test Suite:
https://wpt.fyi/results/css/css-color/

Abstract

This module extends CSS Color [css-color-4] to add color modification functions, custom color spaces (ICC profiles), contrast-color(), light-dark() and device-cmyk().

CSS is a language for describing the rendering of structured documents (such as HTML and XML) on screen, on paper, etc.

Status of this document

This is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-color” in the title, like this: “[css-color] …summary of comment…”. All issues and comments are archived. Alternately, feedback can be sent to the (archived) public mailing list www-style@w3.org.

This document is governed by the 18 August 2025 W3C Process Document.

The following features are at-risk, and may be dropped during the CR period:

“At-risk” is a W3C Process term-of-art, and does not necessarily imply that the feature is in danger of being dropped or delayed. It means that the WG believes the feature may have difficulty being interoperably implemented in a timely manner, and marking it as such allows the WG to drop the feature if necessary when transitioning to the Proposed Rec stage, without having to publish a new Candidate Rec without the feature first.

1. Introduction

This section is not normative.

This module adds the new functions contrast-color(), color-mix() and light-dark(), and extends existing ones with relative color syntax.

It also extends the color() function so that not only predefined color spaces, but also custom color spaces defined by ICC profiles (including calibrated CMYK) can be used in CSS.

It also adds device-cmyk, a representation of uncalibrated cmyk color.

2. The <color> syntax

Colors in CSS are represented by the <color> type:

<color> = <color-base> | currentColor | <system-color> | 
      <contrast-color()> | <device-cmyk()>  | <light-dark-color>

<color-base> = <hex-color> | <color-function> | <named-color> | <color-mix()> | transparent
<color-function> = <rgb()> | <rgba()> |
              <hsl()> | <hsla()> | <hwb()> |
              <lab()> | <lch()> | <oklab()> | <oklch()> |
              <alpha()> |
              <color()>

An absolute color is a <color> whose computed value has an absolute, colorimetric interpretation. This means that the value is not:

Nor are any of those values used inside <color-mix()> or in relative color syntax.

The colors that resolve to sRGB are:

The functions that support legacy color syntax are:

The <hsl()>, <hsla()>, <hwb()>, <lch()>, and <oklch()> color functions are cylindrical polar color representations using a <hue> angle; the other color functions use rectangular orthogonal color representations.

3. Mixing Colors: the color-mix() Function

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.

LC color picker
chloropleth map of the US

Above, a color picker operating in CIE LCH space. Here, a pair of colors are being used to define a color scale on the Chroma-Lightness plane (constant Hue). Below, the color scale in use on a choropleth map.

Currently Sass, calc() on HSL values, or PostCSS is used to do this. However, 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).

To meet this need, the color-mix() function takes a list of one or more <color> specifications and returns the result of mixing them, in a given <color-space>, in the specified amounts.

color-mix() = color-mix( <color-interpolation-method>? , [ <color> && <percentage [0,100]>? ]#)
Tests

3.1. Colorspace for mixing

If no color interpolation method is specified, assume Oklab. Otherwise, use the specified colorspace for mixing.

For example, these two are exactly equivalent:
color-mix(in oklab, firebrick, goldenrod)
color-mix(firebrick, goldenrod)

3.2. Percentage Normalization

Percentages are required to be in the range 0% to 100%. Negative percentages are specifically disallowed. Percentages are normalized by normalizing mix percentages.

Tests
These syntactic forms are thus all equivalent:
color-mix(in lch, purple 50%, plum 50%)
color-mix(in lch, purple 50%, plum)
color-mix(in lch, purple, plum 50%)
color-mix(in lch, purple, plum)
color-mix(in lch, plum, purple)
color-mix(in lch, purple 80%, plum 80%)

All produce a 50-50 mix of purple and plum, in lch: lch(51.51% 52.21 325.8) which is rgb(68.51% 36.01% 68.29%).

However, this form is not the same, as the alpha is less than one:

color-mix(in lch, purple 30%, plum 30%)

This produces lch(51.51% 52.21 325.8 / 0.6) which is rgb(68.51% 36.01% 68.29% / 0.6).

3.3. Calculating the Result of color-mix

To calculate a color-mix():
  1. Normalize mix percentages from the list of mix items passed to the function, with the "forced normalization" flag set to true, letting items and leftover be the result.

  2. Let alpha mult be 1 - leftover, interpreting leftover as a number between 0 and 1.

  3. If items is length 1, set color to the color of that sole item, converted to the specified interpolation <color-space>.

    Otherwise:

    1. Let item stack be a stack made by reversing items. (Thus, with the first item at the top of the stack.)

    2. While item stack has length 2 or greater:

      1. Pop from item stack twice, letting a and b be the two results in order. Let combined percentage be the sum of a and b’s percentages.

      2. Interpolate a and b’s colors as described in CSS Color 4 §  13. Color Interpolation, with a progress percentage equal to (b’s percentage) / combined percentage), if combined percentage is greater than 0, and 0.5 otherwise. If the specified color space is a cylindrical polar color space, then the <hue-interpolation-method> controls the interpolation of hue, as described in CSS Color 4 § 13.4 Hue Interpolation. If no <hue-interpolation-method> is specified, assume shorter.

      3. Create a new mix item with the resulting color and a percentage of combined percentage, and push it onto item stack.

    3. Set color to the color of the sole remaining item in item stack.

  4. Multiply the alpha component of color by alpha mult.

  5. Return color.

Note: In cylindrical polar color spaces, mixing is order-dependent, as which direction is “shorter” or “longer” around the hue circle can change depending on what other mixes have already been performed. This algorithm mixes each color in the specified order, mixing the result with the next in the list. For rectangular orthogonal color spaces, the order doesn’t matter, and the process can be simplified.

Tests
This example produces a mixture of 40% peru and 60% palegoldenrod.
color-mix(in lch, peru 40%, palegoldenrod)

The mixing is done in lch color space. Here is a top-down view, looking along the neutral L axis:

A mixture of two colors, and the mixed output. We are looking down the CIE L axis onto the ab plane. There are two axes, labelled a and b which cross at the origin, which is in the centre of the plot.

Mixtures of peru and palegoldenrod in CIE LCH. Peru has a hue angle, measured from the positive a axis, of 63.677 degrees while palegoldenrod has a hue angle of 98.834 degrees. Peru has a chroma, or distance from the central neutral axis, of 54.011 while palegoldenrod has a chroma of 31.406. All possible mixtures lie along the curve. A 40%/60% mixture is shown.

The calculation is as follows:

This example produces the mixture of teal and olive, in lch color space, with each lch component being 65% of the value for teal and 35% of the value for olive.

Note: interpolating on hue and chroma keeps the intermediate colors as saturated as the endpoint colors.

color-mix(in lch, teal 65%, olive);

A mixture of two colors, and the mixed output. We are looking down the CIE L axis onto the ab plane. There are two axes, labelled a and b which cross at the origin, which is in the centre of the plot.

Mixtures of teal and olive. Teal has a hue angle, measured from the positive a axis, of 196.4524 degrees while olive has a hue angle of 99.5746 degrees. Teal has a chroma, or distance from the central neutral axis, of 31.6903 while olive has a chroma of 56.8124. Mixtures lie along the dashed curve. A 65%/35% mixture is shown.

The calculation is as follows:

In this example, both percentages are zero, so their sum is also zero:
color-mix(in oklch, teal 0%, olive 0%);

Thus, the result is transparent black, in the oklch color space:
oklch(0% 0 none / 0)

In this example three colors are mixed, and no percentages are given so each color contributes one-third of the final result.
color-mix(in oklab, teal, olive, blue);

The calculation is as follows:

3.4. Effect of Mixing Color Space on color-mix

The choice of mixing color space can have a large effect on the end result.

This example is a 50% mix of white and black, in three different color spaces.
color-mix(in lch, white, black);
color-mix(in xyz, white, black);
color-mix(in srgb, white, black);

The calculation is as follows:

The mix in LCH gives an L value of 50%, a perfect mid gray, exactly as expected (mixing in Lab would do the same, as the Lightness axis is the same in LCH and Lab).

The mix in XYZ gives a result that is too light; XYZ is linear-light but is not perceptually uniform. The mix in sRGB gives a result that is a bit too light; sRGB is neither perceptually uniform nor linear-light.

This example produces the mixture of the a red and a sky blue, in xyz color space, with the mixture being 75.23% of that of the red (and thus, 24.77% of that of the blue).
color-mix(in xyz, rgb(82.02% 30.21% 35.02%) 75.23%, rgb(5.64% 55.94% 85.31%));

The calculation is as follows:

This example is a 50% mix of white and blue, in three different color spaces.

color-mix(in lch, white, blue);
color-mix(in oklch, white, blue);
color-mix(in srgb, white, blue);

The calcuation is as follows:

This example is a mix of two colors, in hsl color space, where one of the colors to be mixed is outside the sRGB gamut.
color-mix(in hsl, color(display-p3 0 1 0) 80%, yellow);

The calcuation is as follows:

device-cmyk() can be used in color-mix() but the result will depend on how the implementation chooses to obtain a computed value.
color-mix(in lab, device-cmyk(0.091777 0.043303 0.312816 0.000000) 100%, yellow);

Since the first color is at 100%, the second color is 0% and does not affect the mixed result in any way. The result is thus the computed value of the first color, in CIE Lab.

To visualize the result, let us say that the device CMYK values are in fact to be printed using SWOP 2006 coated.

Suppose the implementation uses an ICC profile to obtain lab() colors, and in this example a FOGRA39 Coated profile is used:

Now suppose another implementation uses the naive color conversion algorithm, giving an sRGB result.

3.5. Effect of Non-Unity Alpha on color-mix

So far, all the color-mix() examples have used fully opaque colors. To simplify the examples, the premultilication and unpremultiplication steps were omitted because these would simply multiply by 1, and divide by 1, so the result would be unchanged.

In the general case, colors may have non-unity alpha components and thus the premultiply, interpolate, unpremultiply steps must not be omitted.

This example is 25% semi-opaque red and 75% semi-opaque green. mixed in sRGB. Both the correct (premultiplied) and incorrect (non-premultiplied) workings are shown.
color-mix(in srgb, rgb(100% 0% 0% / 0.7) 25%, rgb(0% 100% 0% / 0.2));

The calcuation is as follows:

The incorrect calculation would be:

This is a huge difference; the ΔE2000 between the correct and incorrect results is 30.7!

When the percentage normalization generates an alpha multiplier, the calculation is the same except for an additional last step.

This example is similar to the previous one, 25% semi-opaque red and 75% semi-opaque green. mixed in sRGB.

However in this case the percentages are specified as 20% of the first color and 60% of the second. This adds to 80% so the alpha multiplier is 0.8.

The mix percentages are then scaled by a factor of 100/80:
20% * 100/80 = 25%
60% * 100/80 = 75%
giving the same final mix percentages as the previous example.

color-mix(in srgb, rgb(100% 0% 0% / 0.7) 20%, rgb(0% 100% 0% / 0.2) 60%);

The calcuation is as follows:

Note: do not multiply the interpolated alpha by the alpha multiplier and then use that to undo premultiplication. That would be correct if the mix percentages were not scaled to sum to 100%, but they are, so doing it this way would adjust the mixed color twice.

4. Relative Colors

4.1. Processing Model for Relative Colors

In previous levels of this specification, the color functions could only specify colors in an absolute manner, by directly specifying all of the color components.

The new relative color syntax extends modern color syntax to allow existing colors to be modified using the color functions: if an origin color is specified, then each color component (and the alpha component, if specified) can either be directly specified, or taken from the origin color (and possibly modified with math functions).

Most relative colors allow modification of both the color components and the alpha component. However, there is a simpler and more restricted form in which only the alpha component is modified.

For example, here we use relative colors to make a less vibrant variant of an existing color:
--base:  gold;
--softerbase:  oklch(from var(--base) l calc(c * 0.9) h);

While here we change just the opacity:

--marker:  teal;
--palemarker:  alpha(from var(--marker) / 0.7);

The origin color and the relative color need not use the same color function. Thus, we define a relative color processing space, which is the color space within which computation of color values takes place; this also affects the serialization of the result of the relative color.

Conversion, if required: All operations take part in the relative color processing space. If the originally specified color space for the origin color used a different color function, it’s first converted into the relative color processing space, so it has meaningful values for the components, and color component keywords refer to that color space.

If the alpha value of the relative color is omitted, it defaults to that of the origin color (rather than defaulting to 100%, as it does in the absolute syntax).

When relative color syntax is used, color component values, whether directly specified or arising from color space conversion, are not clamped to the reference ranges but are retained as-is. This preserves out of gamut values, if the destination color space is capable of representing them.

However, when relative color syntax is used, alpha component values whether directly specified or arising from color space conversion, are clamped to the reference range.

Missing components are handled the same way as with CSS Color 4 § 13.2 Interpolating with Missing Components: the origin colorspace and the relative function colorspace are checked for analogous components which are then carried forward as missing.

While most uses of relative color syntax will use the component keywords in their corresponding argument, you can use them in any position.

Beware when using components outside their normal position; when percentages are resolved to numbers, there is no "magic scaling" to account for the changed position if those numbers are used in a different place.

There is no relative device-cmyk() syntax.

4.2. Relative Color Syntax

The precise details of each function’s syntactic changes to accommodate relative colors are listed below, but they all follow a common structure: