Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

:has vs :matches - Selectors Level 4

Just wondering what the difference is between CSS (selectors 4) pseudo selectors :has and :matches

The spec http://dev.w3.org/csswg/selectors-4/#overview says:

E:matches(s1, s2)
an E element that matches compound selector s1 and/or compound selector s2
§4.2 The Matches-any Pseudo-class: :matches()

E:has(rs1, rs2)
an E element, if either of the relative selectors rs1 or rs2, when evaluated with E as the :scope elements, match an element
§4.4 The Relational Pseudo-class: :has()

like image 917
alexrogers Avatar asked Jan 08 '23 20:01

alexrogers


1 Answers

In a nutshell:

  • E:has(rs1, rs2) matches E when a different element F matches any of the selector arguments in relation to E. If you know jQuery's :has() selector, this is exactly the same thing.

  • E:matches(s1, s2) matches E when E itself matches any of the selector arguments. Think of :matches() as the direct opposite of :not(), which matches E if E itself does not match any of the arguments.1 You can also think of :matches() as a pseudo-class version of jQuery's .filter() method.

    This notation is equivalent to concatenating every selector argument with E (provided you can actually concatenate them) such that you have a selector list (E)(s1), (E)(s2). For example, div:matches(.foo, .bar) is equivalent to div.foo, div.bar.

This fundamental difference is demonstrated most straightforwardly with the selectors div:matches(p) and div:has(p):

  • div:has(p) matches any div element that has a p descendant. This is very similar to div p, except the former targets the div and the latter targets the p.

  • Since a div can never be a p, div:matches(p) will never match anything. (Likewise, div:not(p) will match all div elements.)


Here's a more complex example with slightly less absurd selectors:

div:has(.foo, .bar)
div:matches(.foo, .bar)

With the following markup:

<div class="foo"></div> <!-- [1] -->
<div class="bar"></div> <!-- [1] -->

<div class="foo bar">   <!-- [2] -->
  <p></p>
</div>

<div>                   <!-- [3] -->
  <p class="foo"></p>
</div>

<div>                   <!-- [3] -->
  <div>                 <!-- [3] -->
    <p class="bar"></p>
  </div>
</div>

<div>                   <!-- [4] -->
  <div class="foo">     <!-- [5] -->
    <p class="bar"></p>
  </div>
</div>

Which elements are matched by which selectors?

  1. Matched by div:matches(.foo, .bar)
    The first div element has the "foo" class, and the second div element has the "bar" class, so each of these satisfies its respective selector argument in the :matches() pseudo-class.

  2. Matched by div:matches(.foo, .bar)
    The third div element has both classes, so it matches both selector arguments.

    A note on specificity: both of these arguments have equal specificity, making the total specificity (0, 1, 1), but when an element matches multiple selector arguments with different specificity values, the spec says that the specificity is that of the most specific argument that is matched.

  3. Matched by div:has(.foo, .bar)
    Each of these div elements has a descendant element (in this case, a p) with a class that matches its respective selector argument in the :has() pseudo-class.

  4. Matched by div:has(.foo, .bar)
    This div element has a div.foo descendant and a p.bar descendant, therefore it satisfies both relative selector arguments.

    A note on specificity: because :has() is not yet in the fast profile and is therefore tentatively excluded from CSS, the concept of specificity does not apply at all. There are plans to include a limited version of this in the fast profile for use in CSS, but there is nothing concrete as yet. Any new developments will be added at an appropriate time.

  5. Matched by div:matches(.foo, .bar) and div:has(.foo, .bar)
    This div element matches both pseudo-classes:

    • it is .foo (as it has the "foo" class), and
    • it has a descendant with the "bar" class.

    This element would also match div:matches(.foo, .bar):has(.foo, .bar), which would be a valid level 4 selector since a compound selector can have any combination of pseudo-classes.

Another difference between :matches() and :has() is that :has() accepts what's known as a relative selector. A relative selector has a scope; selector scoping is an entire topic in its own right, but for the purposes of :has(), the scoping element is always the element you attach the :has() pseudo-class to. But, more importantly, a relative selector can either have an implicit descendant combinator, or begin explicitly with a combinator such as >, + or ~ — this combinator is what links the rest of the relative selector to its scoping element.

For example, while :has() defaults to an ancestor-descendant relationship, you can pass a relative selector that begins with + and it then becomes an adjacent-sibling relationship: ul:has(+ p) matches any ul element that is directly followed by a p (and not necessarily one that contains a p descendant).

As for :matches(), while the overview table says that it accepts a list of compound selectors, AFAIK it hasn't yet been set in stone whether it will take a list of compound selectors or complex selectors, and in which profile (fast or complete). But a compound selector is simply the new name for what Selectors 3 currently calls a sequence of simple selectors and a complex selector is an entire series of compound selectors and combinators. A relative selector is more like a complex selector in that respect. See this answer for a non-exhaustive list of the various terms used in selectors.


1Yes, that's "arguments" in plural — in Selectors 4, :not() can now accept a list of selectors as opposed to a single simple selector. A much-needed enhancement, but it's also to make it consistent with the other new functional pseudo-classes.

like image 75
BoltClock Avatar answered Jan 21 '23 02:01

BoltClock