You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[css-color-5] Clarification on resolved value of the alpha() function #13994
The computed value serialization of the newly added alpha() RCS function seem inconsistent, and I am not sure why they are doing what they are doing.
When currentcolor is not involved, the tests require that legacy color serialization is maintained.
test_computed_value("color", "alpha(from red / 0.5)", "rgba(255, 0, 0, 0.5)");
But, when currentcolor is involved, it seems to use color() function syntax for serialization, even though the currentColor resolves to a legacy color.
My preference would be that using alpha() always upgrades the color to its most modern form (e.g. rgb() -> color(srgb)), like we do with the other RCS functions (including the parts about maintaining hsl()/hwb() when none is used). It's not a straight analogy, since the other RCS functions do this based on the outer function, not the origin, but I think the principle still holds.
Reacted by Chris Lilley, Emilio Cobos Álvarez and Romain Menke
I filed an issue on the WPT repository for this same serialization issue on the from currentcolor tests: web-platform-tests/wpt#60611 (hadn't seen this one when I filed). Based on the existing rules in CSS Color 4 and 5, it seems that these tests shouldn't be serializing as color() because alpha() uses the same color space as the origin color and therefore uses their serialization rules. Reading the spec, I think a resolved color can only serialize to color(...) if it was originally specified using the color() function.
Oh, you're totally right, I completely missed the Level 5 serializing relative color section and was working off just the Level 4 serializing values section.
My preference would be that using alpha() always upgrades the color to its most modern form (e.g. rgb() -> color(srgb)), like we do with the other RCS functions (including the parts about maintaining hsl()/hwb() when none is used). It's not a straight analogy, since the other RCS functions do this based on the outer function, not the origin, but I think the principle still holds.
I agree with the analogy and have just made that explicit in the spec by defining the relative color processing space which, for the alpha() form, is the color space of the origin color; otherwise, it is the color space of the relative color:
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.
For the alpha-only form, the relative color processing space is that of the origin color.
For the color-affecting forms, the relative color processing space is that of the relative color function.
I then use that term consistently while describing computation, resolution, and serialization. The table in 11.3. Serializing Relative Color Functions also uses that term, and thus we end up with the modern syntax for the serialization of the result of the alpha() function.
If the value is internally represented as an integer between 0 and 255 inclusive (i.e. 8-bit unsigned integer), follow these steps:
So if you are storing alpha a uint8_t then 0.5 will be stored as 128 which, indeed, does not have a [0,100] integer serialization so comes back out as a float with up to six significant figures. While if you are storing alpha as, say, a half float then you store 0.5 and get back 0.5.
This is unfortunate for interop but we are constrained by historical behavior here.
The computed value serialization of the newly added alpha() RCS function seem inconsistent, and I am not sure why they are doing what they are doing.
When currentcolor is not involved, the tests require that legacy color serialization is maintained.
test_computed_value("color", "alpha(from red / 0.5)", "rgba(255, 0, 0, 0.5)");
Per today's edits, those WPT are now explicitly incorrect and the modern serialization should be used for the computed value. But sadly, there need to be two accepted values for the alpha component. So either color(srgb 1 0 0 / 0.5) or color(srgb 1 0 0 / 0.501961)
For this example, I think only color(srgb 1 0 0 / 0.5) is valid since at no point is the alpha component expected to be in an 8-bit internal representation. The steps should be effectively:
Convert red to a color(srgb) form -> color(srgb 1 0 0)
So if you are storing alpha a uint8_t then 0.5 will be stored as 128 which, indeed, does not have a [0,100] integer serialization so comes back out as a float with up to six significant figures. While if you are storing alpha as, say, a half float then you store 0.5 and get back 0.5.
This is unfortunate for interop but we are constrained by historical behavior here.
Per today's edits, those WPT are now explicitly incorrect and the modern serialization should be used for the computed value. But sadly, there need to be two accepted values for the alpha component. So either color(srgb 1 0 0 / 0.5) or color(srgb 1 0 0 / 0.501961)
So, it seems there is already a consensus at the browser implementation level. Based on @weinig’s analysis in the comments above, can we standardize the WPT expected value as color(srgb 1 0 0 / 0.5)?
If the specification does not provide a clear conclusion, it leaves CSS downstream tool implementations without a correct reference. For example, similar issues have arisen in both CSSKit and lightningcss. csskit/csskit#1195
I don't think this needs to be the case. I think there is a good case for saying nested colors maintain maximum precision.
I think the only real historical behavior we have to maintain is that non-nested / absolute rgb() get's serialized using 8-bit components.
If an 8-bit alpha has to round 127.5/255 to 128/255, then 10-bit can store 510/1023 (i.e. 127.50/255). And that would be in line with
16bit, half-float, or float per component is recommended for internal storage).
I would be in favor of updating the bit requirements to apply to both color components and alpha. I don't believe that would be a problem.
I'm also in favor of going forward and mandating a minimum or 16-bit half float per component (both color and alpha) for all non-rgb() resolved colors. WebKit uses 32-bit float per component, mostly because using 16-bit half-float is sufficiently hard to do with all supported platforms, but it is not an issue in the slightest. In practice, web pages do not specify that many color values, so the internal representation used for them is pretty inconsequential. The representation really only starts to matter for things like bitmap formats, but CSS color values don't play into that.
Okay, Serializing alpha values has been split into legacy and modern sections. Legacy is what was there before with the 8-bit option and "if an integer exists" and with variable two to six significant figure output.
Modern requires a minimum 16-bit for round-tripping, and always serializes as six figures (modulo trimming trailing zeroes). I also added an example showing how 8, 10 and 12 bits were insufficient for round-tripping.
Okay, Serializing alpha values has been split into legacy and modern sections. Legacy is what was there before with the 8-bit option and "if an integer exists" and with variable two to six significant figure output.
Modern requires a minimum 16-bit for round-tripping, and always serializes as six figures (modulo trimming trailing zeroes). I also added an example showing how 8, 10 and 12 bits were insufficient for round-tripping.
@svgeesus Given that alpha() and relative colors are defined in CSS Color 5, could you add a serialization example there?
FYI, for the edits related to serializing resolved alpha() values with the modern syntax, I've updated the WPTs on the Gecko side: https://bugzilla.mozilla.org/show_bug.cgi?id=2059737. Should upstream to WPT soon.
I think this note has either a typo or is in the wrong section (or both)? It says the serialization of the declared value of the alpha(...) example is color(...). Shouldn't it either say "result" or "resolved value" so the serialization stays as color(...); or keep "declared value" but have a serialization of alpha(...)?
Activity
weinig commentedon May 31, 2026
My preference would be that using
alpha()always upgrades the color to its most modern form (e.g. rgb() -> color(srgb)), like we do with the other RCS functions (including the parts about maintaining hsl()/hwb() when none is used). It's not a straight analogy, since the other RCS functions do this based on the outer function, not the origin, but I think the principle still holds.[Color] Add support for the alpha() relative color function
kbhomes commentedon Jun 14, 2026
I filed an issue on the WPT repository for this same serialization issue on the
from currentcolortests: web-platform-tests/wpt#60611 (hadn't seen this one when I filed). Based on the existing rules in CSS Color 4 and 5, it seems that these tests shouldn't be serializing ascolor()becausealpha()uses the same color space as the origin color and therefore uses their serialization rules. Reading the spec, I think a resolved color can only serialize tocolor(...)if it was originally specified using thecolor()function.weinig commentedon Jun 15, 2026
No, relative color syntax always (prior to alpha()) produces color(srgb) for things like hsl(from …) and rgb(from …).
kbhomes commentedon Jun 15, 2026
Oh, you're totally right, I completely missed the Level 5 serializing relative color section and was working off just the Level 4 serializing values section.
yisibl commentedon Jun 17, 2026
If serialized as
color(), what is the computed value ofalphain the expressionalpha(from rgba(0, 255, 0, 0.5) / alpha)?Taking the relative color
rgb(from rgba(0, 255, 0, 0.5) r g b / alpha)as an example,128/255 ≈ 0.501961, which is:color(srgb 0 1 0 / 0.501961)0.5Interoperability is currently very poor and there is no WPT test cases.
@weinig What is the current implementation algorithm in WebKit?
cc @LeaVerou
svgeesus commentedon Jun 18, 2026
I agree with the analogy and have just made that explicit in the spec by defining the relative color processing space which, for the
alpha()form, is the color space of the origin color; otherwise, it is the color space of the relative color:I then use that term consistently while describing computation, resolution, and serialization. The table in 11.3. Serializing Relative Color Functions also uses that term, and thus we end up with the modern syntax for the serialization of the result of the
alpha()function.svgeesus commentedon Jun 18, 2026
I don't see anything in Color 4 or 5 that requires doing 8-bit computation on alpha values. 0.5 is clearly the correct result here.
Edit: Ah, actually Serializing alpha values has a historical/legacy carve-out:
So if you are storing alpha a
uint8_tthen0.5will be stored as128which, indeed, does not have a[0,100]integer serialization so comes back out as a float with up to six significant figures. While if you are storing alpha as, say, a half float then you store0.5and get back0.5.This is unfortunate for interop but we are constrained by historical behavior here.
[css-color-5] Defined the term relative color processing space, #13994 …
[css-color-4] Defined resolving of relative colors in terms of the re…
2 remaining items
weinig commentedon Jun 18, 2026
For this example, I think only
color(srgb 1 0 0 / 0.5)is valid since at no point is the alpha component expected to be in an 8-bit internal representation. The steps should be effectively:redto acolor(srgb)form ->color(srgb 1 0 0)color(srgb 1 0 0 / 0.5).weinig commentedon Jun 18, 2026
I don't think this needs to be the case. I think there is a good case for saying nested colors maintain maximum precision.
I think the only real historical behavior we have to maintain is that non-nested / absolute rgb() get's serialized using 8-bit components.
yisibl commentedon Jun 18, 2026
@svgeesus Good news: Chrome is changing its behavior so that the computed value changes from
0.501961to0.5.https://issues.chromium.org/issues/523282988
So, it seems there is already a consensus at the browser implementation level. Based on @weinig’s analysis in the comments above, can we standardize the WPT expected value as
color(srgb 1 0 0 / 0.5)?If the specification does not provide a clear conclusion, it leaves CSS downstream tool implementations without a correct reference. For example, similar issues have arisen in both CSSKit and lightningcss.
csskit/csskit#1195
svgeesus commentedon Jun 19, 2026
I would very much like that to be the case.
We already have minimum precision requirements for color components ranging from 10 bits to 16. But we say nothing about alpha.
If an 8-bit alpha has to round 127.5/255 to 128/255, then 10-bit can store 510/1023 (i.e. 127.50/255). And that would be in line with
svgeesus commentedon Jun 19, 2026
Excellent!
weinig commentedon Jun 20, 2026
I would be in favor of updating the bit requirements to apply to both color components and alpha. I don't believe that would be a problem.
I'm also in favor of going forward and mandating a minimum or 16-bit half float per component (both color and alpha) for all non-rgb() resolved colors. WebKit uses 32-bit float per component, mostly because using 16-bit half-float is sufficiently hard to do with all supported platforms, but it is not an issue in the slightest. In practice, web pages do not specify that many color values, so the internal representation used for them is pretty inconsequential. The representation really only starts to matter for things like bitmap formats, but CSS color values don't play into that.
[css-color-4] Split serialization of alpha values by whether legacy a…
svgeesus commentedon Jun 21, 2026
Okay, Serializing alpha values has been split into legacy and modern sections. Legacy is what was there before with the 8-bit option and "if an integer exists" and with variable two to six significant figure output.
Modern requires a minimum 16-bit for round-tripping, and always serializes as six figures (modulo trimming trailing zeroes). I also added an example showing how 8, 10 and 12 bits were insufficient for round-tripping.
yisibl commentedon Jun 22, 2026
@svgeesus Given that
alpha()and relative colors are defined in CSS Color 5, could you add a serialization example there?svgeesus commentedon Jun 23, 2026
Yes, certainly.
kbhomes commentedon Jul 31, 2026
FYI, for the edits related to serializing resolved
alpha()values with the modern syntax, I've updated the WPTs on the Gecko side: https://bugzilla.mozilla.org/show_bug.cgi?id=2059737. Should upstream to WPT soon.(Edit: WPT PR at web-platform-tests/wpt#61671)
[css-color-5][editorial] Add examples of RCD alpha() serialization, #…
svgeesus commentedon Jul 31, 2026
Here is the example
kbhomes commentedon Aug 1, 2026
I think this note has either a typo or is in the wrong section (or both)? It says the serialization of the declared value of the
alpha(...)example iscolor(...). Shouldn't it either say "result" or "resolved value" so the serialization stays ascolor(...); or keep "declared value" but have a serialization ofalpha(...)?