Skip to content

[css-color-5] Clarification on resolved value of the alpha() function #13994

Open

Description

@weinig

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.

function test_currentcolor_alpha(specified, expected, current) {
    target.style["color"] = current;
    fuzzy_test_computed_color_property("background-color", specified, expected);
}

test_currentcolor_alpha("alpha(from currentcolor / 0.5)", "color(srgb 1 0 0 / 0.5)", "red");

Is there a rule I am missing that specifies one vs. another?

cc @svgeesus

Activity

  1. weinig commented on May 31, 2026

    @weinig
    ContributorAuthor

    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.

  2. self-assigned this
    on Jun 1, 2026
  3. kbhomes commented on Jun 14, 2026

    @kbhomes
    Contributor

    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.

  4. weinig commented on Jun 15, 2026

    @weinig
    ContributorAuthor

    No, relative color syntax always (prior to alpha()) produces color(srgb) for things like hsl(from …) and rgb(from …).

  5. kbhomes commented on Jun 15, 2026

    @kbhomes
    Contributor

    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.

  6. yisibl commented on Jun 17, 2026

    @yisibl
    Contributor

    If serialized as color(), what is the computed value of alpha in the expression alpha(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,

    • Chrome calculates it as 128/255 ≈ 0.501961, which is: color(srgb 0 1 0 / 0.501961)
    • Safari and Firefox calculate it as 0.5

    Interoperability is currently very poor and there is no WPT test cases.

    @weinig What is the current implementation algorithm in WebKit?

    cc @LeaVerou

  7. svgeesus commented on Jun 18, 2026

    @svgeesus
    Collaborator

    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.

  8. svgeesus commented on Jun 18, 2026

    @svgeesus
    Collaborator

    Taking the relative color rgb(from rgba(0, 255, 0, 0.5) r g b / alpha) as an example,

    * Chrome calculates it as **`128/255 ≈ 0.501961`**, which is: `color(srgb 0 1 0 / 0.501961)`
    

    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:

    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.

  9. 2 remaining items

  10. weinig commented on Jun 18, 2026

    @weinig
    ContributorAuthor

    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)
    • Replace alpha with 0.5 -> color(srgb 1 0 0 / 0.5).
  11. weinig commented on Jun 18, 2026

    @weinig
    ContributorAuthor

    This is unfortunate for interop but we are constrained by historical behavior here.

    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.

  12. yisibl commented on Jun 18, 2026

    @yisibl
    Contributor

    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)

    @svgeesus Good news: Chrome is changing its behavior so that the computed value changes from 0.501961 to 0.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

  13. svgeesus commented on Jun 19, 2026

    @svgeesus
    Collaborator

    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.

    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

    16bit, half-float, or float per component is recommended for internal storage).

  14. svgeesus commented on Jun 19, 2026

    @svgeesus
    Collaborator

    @svgeesus Good news: Chrome is changing its behavior so that the computed value changes from 0.501961 to 0.5.
    https://issues.chromium.org/issues/523282988

    Excellent!

  15. weinig commented on Jun 20, 2026

    @weinig
    ContributorAuthor

    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.

    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

    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.

  16. svgeesus commented on Jun 21, 2026

    @svgeesus
    Collaborator

    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.

  17. yisibl commented on Jun 22, 2026

    @yisibl
    Contributor

    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?

  18. svgeesus commented on Jun 23, 2026

    @svgeesus
    Collaborator

    @svgeesus Given that alpha() and relative colors are defined in CSS Color 5, could you add a serialization example there?

    Yes, certainly.

  19. kbhomes commented on Jul 31, 2026

    @kbhomes
    Contributor

    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)

  20. svgeesus commented on Jul 31, 2026

    @svgeesus
    Collaborator

    @svgeesus Given that alpha() and relative colors are defined in CSS Color 5, could you add a serialization example there?

    Here is the example

  21. kbhomes commented on Aug 1, 2026

    @kbhomes
    Contributor

    Here is the example

    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(...)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions