Skip to content

[css-conditional-5][css-color] Define style() color comparison in more precise terms #13157

Open

Description

@LeaVerou

https://drafts.csswg.org/css-conditional-5/#container-rule

Unless I missed it, the way values are expected to match in style() queries is largely left undefined, with some non-normative prose around matching computed values. As a result, implementations vary dramatically in how they handle this, which is particularly noticeable in color values.

Here are some tests that compare certain color values to black: https://codepen.io/leaverou/pen/WbwMOOW?editors=1100

Blink:

Image

WebKit:

Image

(Gecko shows all red, @emilio might know why — yes the about:config option is on)

But even where they agree, the results are nonsensical. From an author perspective all of these should be green, because they are all exactly the same color! The only one that could plausibly not match is rgb(-10 0 0) and that matches in both impls. The reasons they don't match are largely spec/implementation details and should not be leaking into usage like this.

I suspect we can still fix this as style() is only now rolling out in Gecko, and authors are hesitant to use it until it does. Chrome’s use counter shows 0.4% for style queries but only a small subset of those are going to be registered colors. Time is of the essence though.

I propose we define a rule similar to interpolation in which we first convert colors to the same color space and compare component-wise following powerless component rules too.

cc @svgeesus @tabatkins

Activity

  1. SebastianZ commented on Nov 25, 2025

    @SebastianZ
    Contributor

    (Gecko shows all red, @emilio might know why — yes the about:config option is on)

    The current implementation is incomplete, see https://bugzil.la/1945342.

    Sebastian

  2. svgeesus commented on Mar 23, 2026

    @svgeesus
    Collaborator

    Firefox has improved since:

    Image

    I propose we define a rule similar to interpolation in which we first convert colors to the same color space and compare component-wise following powerless component rules too.

    That seems like a very good way forward, it is consistent with how we deal with disparate color spaces elsewhere and avoids surprises.

    Which spec would be a good home for this?

  3. tabatkins commented on Mar 23, 2026

    @tabatkins
    Member

    Unless I missed it, the way values are expected to match in style() queries is largely left undefined, with some non-normative prose around matching computed values.

    I believe the intention is that this should be the exact same matching behavior as Transitions uses. (Which is indeed largely hand-waved.)

    Alltho the Transitions behavior is technically observable (via transition events), I agree that a style() query is much more observable, and so figuring out how to make this clearer and more consistent is important.

  4. svgeesus commented on Mar 23, 2026

    @svgeesus
    Collaborator

    Strawperson for color comparisons (adapted from 12. Color Interpolation):

    Comparing colors

    Comparison of two values for equality takes place by executing the following steps:

    • checking the two colors for analogous components which will be carried forward
    • converting them to a given color space which will be referred to as the comparison color space below. If one or both colors are already in the comparison color space, this conversion changes any powerless components to missing values. If no comparison color space is specified, oklab is used
    • (if required) re-inserting carried forward values in the converted colors
    • the two colors are compared, component by component, for equality. To accommodate floating point errors due to color space conversion, implementations will return true if the difference between two components is less than some small value ε
    • return true if all components (the color components, and the alpha component) are equal

    For interoperability, defining ε should be done in the spec, at least for oklab.

    It could well be that we don't need any other comparison color space than oklab but I prefer not to assume that at this stage.

  5. LeaVerou commented on Mar 23, 2026

    @LeaVerou
    MemberAuthor

    I think we need to decide what we want to do for none components before we author spec text. Currently the algorithm above would return true for e.g. hsl(none 100% 50%) vs yellow. Do we want that? My intuition is no, but we should discuss.

  6. svgeesus commented on Mar 24, 2026

    @svgeesus
    Collaborator

    Currently the algorithm above would return true for e.g. hsl(none 100% 50%) vs yellow. Do we want that?

    No. Although I would be fine with oklab(none none none / none) matching everything. But the example you gave is clearly not (just) yellow.

  7. svgeesus commented on Mar 24, 2026

    @svgeesus
    Collaborator

    So we want the powerless to missing part, but not the carry-forward part.

  8. LeaVerou commented on Mar 24, 2026

    @LeaVerou
    MemberAuthor

    Ok so I think none should not match anything other than none. If you want to resolve nones, you can just wrap your color in a color-mix(). Aside from that, color space conversion should be fine, and if it preserves missing components, all the better.
    So if color space conversion invokes powerless-to-missing, that's fine, you'll just get false. Which color space you convert to shouldn't matter, since powerless component groups are symmetrical.

    Though if we go down the route of picking an epsilon, it would need to be different not just per color space, but also per component. I wonder if a better strategy may be to compute deltaEOK (which is computationally cheap once you convert to oklab) and use a fraction of the JND. Though that doesn't help when you have powerless components, so we may need to go down that route anyway…

  9. moved this from Agenda+ FTF to Tuesday afternoon in CSSWG spring 2026 meeting agendaon Mar 26, 2026
  10. 21 remaining items

  11. svgeesus commented on Apr 17, 2026

    @svgeesus
    Collaborator

    @astearns it seems we are close to consensus here; if you agree, proposed async resolution.

  12. astearns commented on Apr 20, 2026

    @astearns
    Member

    The CSSWG will automatically accept this resolution one week from now if no objections are raised here. Anyone can add an emoji to this comment to express support. If you do not support this resolution, please add a new comment.

    Proposed Resolution: Define ε as 0.00001 for oklab

  13. astearns commented on Apr 27, 2026

    @astearns
    Member

    RESOLVED: Define ε as 0.00001 for oklab

  14. yisibl commented on Sep 10, 2026

    @yisibl
    Contributor

    Hi @svgeesus

    I ran a test in your tools. Why do hwb(320deg 30% 40%) and rgb(153, 77, 128) show up as “Not equivalent”?
    https://apps.colorjs.io/equivalent-colors/?a=hwb(320deg%2030%25%2040%25)&b=rgb(153%2C%2077%2C%20128)

    Is this caused by floating-point numbers?
    Image

  15. svgeesus commented on Sep 10, 2026

    @svgeesus
    Collaborator

    Not floating point, but rather the limited precision of quantizing to only 8 bits.

    Those component values differ by more than ε, which my tool puts as the same value as the one CSSWG resolved on for Oklab comparisons.

    I feel that this value should not be left as implementation defined, by the way.

    Note that color equivalence, as defined in section 12, is separate from "visually indistinguishable" which is a much looser classification based on Just Noticeable Differencs (ΔE)..

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions