:Has Vs: Matches - Selectors Level 4

:has vs :matches - Selectors Level 4

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.


1 Yes, 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.

Correct terms and words for sections and parts of selectors

What is the correct term for the sections of CSS selectors that are separated by commas?

These are called complex selectors. The entire comma-separated list is known as a selector-list.

Within those sections, what is the term for the parts separated by combinators (spaces, +, >, etc)?

These are known as compound selectors.

So, a selector-list is made up of one or more complex selectors separated by commas, and each complex selector is made up of two main parts: compound selectors, and combinators. It can also optionally contain pseudo-elements.

Compound selectors used to have the rather convoluted name "sequence of simple selectors". Worse still, complex selectors were just called "selectors". Needless to say, I recommend using the new terms as they are much more straightforward, much less cumbersome and completely unambiguous compared to their predecessors.


And since I'm here, here are the rest of the definitions...

  • A simple selector is one fundamental component of selectors. It is any one of the following:

    • Universal selector (*), optionally with a namespace
    • Type selector (a, div, span, ul, li, etc), optionally with a namespace
    • Attribute selector ([att], [att=val], etc), optionally with a namespace
    • Class selector (.class)
    • ID selector (#id)
    • Pseudo-class (:pseudo-class)
  • As answered above, a compound selector (formerly a "sequence of simple selectors") is a chain of simple selectors not separated by a combinator:

    a:not([rel="external"]):hover
  • A combinator is another fundamental component of selectors. It is a symbol or token that separates two compound selectors, establishing in its place a relationship between the two elements represented by the two compound selectors. There are currently four combinators in use today:

    • Descendant combinator:

      article p
    • Child combinator:

      /* 
      * The extra spaces in between are whitespace,
      * and are therefore insignificant.
      */
      ul > li
    • Adjacent sibling combinator:

      header + section
    • General sibling combinator:

      h2 ~ p

    More combinators may be introduced in later specifications.

  • And a complex selector (formerly just "selector") is a complete string made up of compound selectors linked by combinators:

    nav[role="main"] > ul:first-child > li
  • The subject of a complex selector is its last, or only, compound selector, representing the element that will be matched or styled. In the above example, the subject of the selector is li.

  • The term selector has been generalized, so it may now refer to any of the following for the purposes of simplicity and brevity, and which one it's referring to at any given moment should be gleaned from context:

    • Simple selector
    • Compound selector
    • Complex selector
    • Selector-list (e.g. the "selector" component of a style rule)

Some personal notes:

  • The term "key selector" was coined by browser vendors for use with selector implementations, and is not an official term. It is often used to mean "subject of the selector" however, because implementations happen to use the subject of the selector as the key for the purposes of determining matches.

  • The term "pseudo-selector" was coined by authors to mix pseudo-classes and pseudo-elements, and is not an official, or indeed meaningful, term. Although you can find it in some early-stage W3C CSS2/3 drafts, that was probably a mistake. Please don't use this term, as it needlessly creates confusion by attempting to group two completely different concepts into a single umbrella term.

  • Pseudo-elements (::pseudo-element) are not simple selectors, and therefore cannot appear in places where only actual elements may be matched. However, they are still considered selectors for the purposes of CSS parsing, and as stated above currently can appear at the end of any complex selector in a list (i.e. at the end of the last, or only, compound selector of each complex selector).

  • In CSS, a typical style rule (formerly "ruleset") consists of a selector and a declaration block.

  • Namespace prefixes are not selectors in their own right, but they may be applied to type selectors, universal selectors and attribute selectors to match components in a document that are (or are not) namespaced.

  • The specificity of a selector currently only refers to that of a single complex selector. When matching rules, any of the complex selectors in a list that match a given element will be considered for specificity calculations. If more than one complex selector matches an element, the most specific one will be used for calculations.

    Specificity will be a more complicated issue with some level 4 selectors, such as :is() and the enhanced :not(), and the of S notation in the enhanced :nth-child() pseudo.

Grouping Css Selectors (i.e. child OR selectors)

:any() is implemented in Fx 4+ as :-moz-any() (and Saf 5.1 and Chr 12+ as :-webkit-any(), though I never tested on WebKit), see https://developer.mozilla.org/en-US/docs/CSS/:any

Note : This pseudo-class is in progress to be standardized in CSS Selectors Level 4 under the name :matches(). It is likely that the syntax and name of :-vendor-any() will be changed to reflect it in the near future.

This is quite OK in a media query @-rule (except for recent IE) and otherwise you should add an HTML class to the element you want to style and use this class in your CSS selector. Or use a preprocessor where nesting will save you a few repeats.

See @Christoph answer for the near future and :matches()

Can we achieve anything with the new CSS :is() pseudo-class that we can't already achieve with comma-separated queries?

You are actually dealing with basic examples but consider more complex ones like the following:

.box h1, .box h2, .box h3, .box h4

In order to avoid repeating the .box we use

.box :is(h1, h2, h3, h4)

As far as I know, this was the main motivation for :is(): avoid rule duplication.

Another common example is the table selectors:

table tr td, table tr th

now will become

table tr :is(td, th)

CSS3 combining selectors with OR instead of AND

You'll need to split them up using a comma:

body[class*="page-node-add-"], body[class~="page-node-edit"] {background:red;}

The problem with using a comma:

... is that you can't do it any other way than with a comma. Perhaps it could have been remedied with Selectors 3, but unfortunately the spec says otherwise. That is only going to be remedied by Selectors 4, either because it wasn't proposed until recently, or it was proposed but didn't make the cut for level 3.

In level 4 of Selectors you will be able to do something like this:

body:matches([class*="page-node-add-"], [class~="page-node-edit"]) form.node-form > .field-type-field-collection > table > thead tr th
{...}

Currently, this is being implemented under its originally-proposed name, :any(), with the prefixes :-moz-any() and :-webkit-any(). But using :any() in public-facing CSS is pointless given that

  1. only Gecko and WebKit support it; and

  2. you have to duplicate your rulesets because of the way prefixed selectors are handled, which not only defeats the intended purpose of the :matches() selector, but makes things even worse:

    body:-moz-any([class*="page-node-add-"], [class~="page-node-edit"]) form.node-form > .field-type-field-collection > table > thead tr th
    {...}
    body:-webkit-any([class*="page-node-add-"], [class~="page-node-edit"]) form.node-form > .field-type-field-collection > table > thead tr th
    {...}

In other words, until implementations update themselves to the standardized :matches(), there is no other viable solution (save from using a preprocessor to generate the repeated selectors for you).

What is the difference between :where() and :is()?

As mentioned, the difference is specificity. This is mentioned on MDN, though not prominently for some reason. The spec, on the other hand, is much more explicit about it:

4.4. The Specificity-adjustment Pseudo-class: :where()

The Specificity-adjustment pseudo-class, :where(), is a functional pseudo-class with the same syntax and functionality as :is(). Unlike :is(), neither the :where pseudo-class, nor any of its arguments contribute to the specificity of the selector—its specificity is always zero.

What are the use cases? Well, in the example you've given, it's not terribly useful. You don't have any competing selectors for which you need to match or otherwise not override specificity. You have a basic span rule, and a :hover rule that overrides it naturally (i.e. just by how specificity works and what specificity was originally designed for). If there aren't any special or exceptional styles you need to take into account, it doesn't really matter whether you use :is() or :where().

:where() becomes useful when you have more general rules that have unnecessarily specific selectors, and these need to be overridden by more specialized rules that have less specific selectors. Both MDN and the spec contain an example of a (very) common use case — I don't want to simply regurgitate what's on MDN, so here's the one from the spec:

Below is a common example where the specificity heuristic fails to match author expectations:

a:not(:hover) {
text-decoration: none;
}

nav a {
/* Has no effect */
text-decoration: underline;
}

However, by using :where() the author can explicitly declare their intent:

a:where(:not(:hover)) {
text-decoration: none;
}

nav a {
/* Works now! */
text-decoration: underline;
}

Unlike MDN, the spec doesn't really explain this use case in English, so I will. The "author expectations" here are that the nav a CSS rule (what I call a specialized rule) would override the a:not(:hover) rule (what I call a general rule). Ostensibly, this does not happen.

Because the :hover pseudo-class is more specific than type selectors, any rules that have only type selectors won't be able to override the general a:not(:hover) rule that applies to any a that isn't hovered. Traditionally you'd have needed to match the specificity of a:not(:hover), most naïvely by duplicating the offending bit:

a:not(:hover) {
text-decoration: none;
}

nav a, nav a:not(:hover) {
/* Works, but not ideal, to say the least */
text-decoration: underline;
}

Alternatively, by adding selectors that increase specificity without affecting matching:

a:not(:hover) {
text-decoration: none;
}

nav a:nth-child(n) {
/* Works, but not ideal either */
text-decoration: underline;
}

(or a:any-link, in the case of links)

What :where() does is allow you to remove the specificity added by :hover altogether, thereby making it much easier to override this rule for certain a elements, for example by ensuring that a elements in a nav are always underlined whether or not the cursor is over them (since nav a is more specific than just a).

Unusually, because it decreases a selector's specificity, you'd generally use :where() with selectors that need to be overridden, rather than the overriding selectors. On the other hand, you use :is() simply to reduce selector duplication in your CSS rules, e.g. by changing

.span1-1:hover, .span1-2:hover

to

:is(.span1-1, .span1-2):hover

while preserving specificity (though do note that :is() does work differently once you're grouping selectors with different specificity amounts).

This means that while :where() has a similar syntax to :is(), in practice they're two different pseudo-classes with different use cases. Their use cases do partially overlap nevertheless. For example, you may need to reduce selector duplication in a general rule, which implies using :is(), but you'd prefer :where() instead to also reduce specificity. Since it's useful to apply a single :where() to multiple selectors, allowing it to accept a selector-list makes it so you don't have to write :where(:is(selector-list)). This principle applies to many other new pseudo-classes such as :host(), :host-context() and :has(), as well as the level 4 :not().

CSS3 selector :first-of-type with class name?

No, it's not possible using just one selector. The :first-of-type pseudo-class selects the first element of its type (div, p, etc). Using a class selector (or a type selector) with that pseudo-class means to select an element if it has the given class (or is of the given type) and is the first of its type among its siblings.

Unfortunately, CSS doesn't provide a :first-of-class selector that only chooses the first occurrence of a class. As a workaround, you can use something like this:

.myclass1 { color: red; }
.myclass1 ~ .myclass1 { color: /* default, or inherited from parent div */; }

Explanations and illustrations for the workaround are given here and here.



Related Topics



Leave a reply



Submit