A little thing happened on the way to publishing the CSS :has()
selector to the ol’ Almanac. I had originally described :has()
as a “forgiving” selector, the idea being that anything in its argument is evaluated, even if one or more of the items is invalid.
/* Example: Do not use! */
article:has(h2, ul, ::-scoobydoo) { }
See ::scoobydoo
in there? That’s totally invalid. A forgiving selector list ignores that bogus selector and proceeds to evaluate the rest of the items as if it were written like this:
article:has(h2, ul) { }
:has()
was indeed a forgiving selector in a previous draft dated May 7, 2022. But that changed after an issue was reported that the forgiving nature conflicts with jQuery when :has()
contains a complex selector (e.g. header h2 + p
). The W3C landed on a resolution to make :has()
an “unforgiving” selector just a few weeks ago.
So, our previous example? The entire selector list is invalid because the bogus selector is invalid. But the other two forgiving selectors, :is()
and :where()
, are left unchanged.
There’s a bit of a workaround for this. Remember, :is()
and :where()
are forgiving, even if :has()
is not. That means we can nest either of the those selectors in :has()
to get more forgiving behavior:
article:has(:where(h2, ul, ::-scoobydoo)) { }
Which one you use might matter because the specificity of :is()
is determined by the most specific item in its list. So, if you need to something less specific you’d do better reaching for :where()
since it does not add to the specificity score.
/* Specificity: (0,0,1) */
article:has(:where(h2, ul, ::-scoobydoo)) { }
/* Specificity: (0,0,2) */
article:has(:is(h2, ul, ::-scoobydoo)) { }
We updated a few of our posts to reflect the latest info. I’m seeing plenty of others in the wild that need to be updated, so just a little PSA for anyone who needs to do the same.
Thanks for this post!
Note that the implementation in Safari never was forgiving when it first shipped, so you couldn’t rely on that perk anyway.
Thanks Geoff for the heads up!
This article is funny to me b/c it’s so css’y. The only thing that would make it funnier is if :has was still forgiving after the jquery fix but only in ie6.