Surprising Facts About New CSS Selectors

Created on November 12, 2023 at 11:07 am

I went down a bit of a rabbit-hole recently learning about CSS Nesting ORG , specifically regarding the new & selector. I heard that it behaves like the :is() selector, and in the course of researching, I learned a lot about how these new selectors work.

The first ORDINAL thing to know is that is() and its siblings :not() , :has() , and :where() are a new type of selector called functional pseudo-classes. We’ve had simple pseudo-classes like :hover for a long time, but the ability to pass a selector list as a parameter is what distinguishes these four CARDINAL into a new category.

Here’s a quick review of the new selectors we’ll be discussing today DATE , and then we’ll dive into the surprising things I learned:

The Matches-Any Pseudo WORK_OF_ART -class, :is() , accepts a comma-separated list of selectors and matches any element that can be selected by one CARDINAL of the items in the list. For example, :is(article, section, aside) h1 will match any h1 element that is contained in an article , section , or aside element.

, accepts a comma-separated list of selectors and matches any element that can be selected by one CARDINAL of the items in the list. For example, will match any element that is contained in an , , or element. The Negation (or Matches-None) Pseudo-class, :not() , represents elements that do not match a list of selectors. For example, li:not(:last-child) will select any list item that is not the last item in the list.

, represents elements that do not match a list of selectors. For example, will select any list item that is not the last item in the list. The Relational Pseudo-class, :has() , is a way to select a parent element based on its children or siblings. For example, h1:has(+ p) will only apply to h1 elements that are immediately followed by a p element.

, is a way to select a parent element based on its children or siblings. For example, will only apply to elements that are immediately followed by a element. The Specificity-adjustment Pseudo-class, :where() , behaves like the :is() selector, but its specificity is always zero CARDINAL .

, behaves like the selector, but its specificity is always zero CARDINAL . The Nesting Selector, & ORG , represents the elements matched by the parent rule in CSS Nesting ORG , and behind the scenes, it’s treated as an :is() selector.

Permalink to Specificity Specificity

One CARDINAL of the most interesting properties of these new selectors is how they interact with specificity. A deep dive on specificity is beyond the scope of this article, but it’s enough to understand that an ID is more specific than a class, which is more specific than an element selector.

#unique { color : red; } .intro CARDINAL { color : orange } p { color : green; } Code language: CSS ( css )

< p class = " intro WORK_OF_ART " id = "unique" > This will be red </ p > Code language: Handlebars ( handlebars )

The paragraph will be red, because the ID selector is the most specific.

So what happens with a pseudo-class that accepts a comma-separated list of selectors, like :is() , :not() , and :has() ? The specificity of the pseudo-class is determined by the most specific selector in the list.

:is( #unique , p ) { color : red; } .intro CARDINAL { color : green; } Code language: CSS ( css )

< p class = " intro WORK_OF_ART " > This will also be red </ p > Code language: Handlebars ( handlebars )

You might expect the paragraph to be green, because it doesn’t have the #unique ID. But in this case, the most specific item in the list is the ID, so that will be the specificity of the :is() selector. As a result, even though the ID doesn’t apply in this case, it still affects the specificity.

One CARDINAL interesting variation on this behavior is the :where() selector, which behaves exactly the same as the :is() selector, except it always has zero CARDINAL specificity.

:where( #unique , .intro ) { color : red; } p { color : green; } Code language: CSS ( css )

< p class = " intro WORK_OF_ART " id = "unique" > This will be green. </ p > Code language: Handlebars ( handlebars )

This is especially useful if you’re writing CSS that you want to be easy to override, such as the default styles in a WordPress ORG theme or a third ORDINAL -party library.

Permalink to Forgiving Selector Lists Forgiving Selector Lists

When you construct a selector list in standard CSS, if any selector in the list is invalid — such as .valid-class, :invalid-pseudo-class — the entire style block will be ignored.

Now, you might be thinking “Who would add an invalid selector to their CSS?” An example of when you might have to do this is browser-prefixed selectors. Say if you wanted to use the :fullscreen pseudo-class, but needed to support some older browsers that only understand the webkit ORG prefixed version. You would need to write this:

:-webkit-full-screen { border-color : green; } :fullscreen { border-color : green; } Code language: CSS ( css )

If you tried to use a comma-separated list, then a browser that doesn’t understand :fullscreen would fail. As a result, you have to duplicate the style block for each selector.

Thankfully, most of the new pseudo-classes, like :is() and :where() , accept what the specification calls a forgiving selector list, meaning any invalid selectors in the list will be ignored, but the valid ones will still be used.

:is(.valid-class , :invalid-pseudo-class) { … } Code language: CSS ( css )

The browser will ignore :invalid-pseudo-class but still apply the rule to .valid-class .

Unfortunately, the :not() pseudo-class uses the unforgiving selector list, so adding any invalid selectors to the list will cause the entire style block to be ignored. However, there’s a rather silly solution: Just wrap the selector list in an :is() pseudo-class:

:not( :is(.valid-class , :invalid-pseudo-class)) { … } Code language: CSS ( css )

This has the effect of letting :is() strip any invalid selectors from the list, and pass the remaining selectors on to :not() .

Until recently, the :has() pseudo-class also used the forgiving selector list, but they had to revert this in late 2022 DATE to avoid a conflict with jQuery. Now, if you might have an invalid selector being passed to :has() , you can use the same :has(:is()) trick to prevent it from breaking.

Permalink to Things to Note Things to Note

There are a few things about these new selectors that may surprise you if you’re not careful. Here are a few that I’ve noticed:

You can’t select pseudo-elements using the new selectors. Writing a:is(::before, ::after) won’t work. The reason for this is complicated.

won’t work. The reason for this is complicated. Be careful! .a .b .c is not the same as .a : is(.b EVENT .c) . The first ORDINAL matches any .c that is a child of .b that is a child of .a . The second ORDINAL matches any .c that is a child of .a and .b , regardless of order! Learn more in this excellent post by Bramus Van Damme PERSON

is not the same as . The first ORDINAL matches any that is a child of that is a child of . The second ORDINAL matches any that is a child of and , regardless of order! Learn more in this excellent post by Bramus Van Damme PERSON The :not() selector will match everything that is “not an X”. For instance, body :not(table) a will still apply to links inside a <table> , since <tr> , <tbody> , <th> , <td> , <caption> , etc. can all match the :not(table) part of the selector.

selector will match everything that is “not an X”. For instance, will still apply to links inside a , since , , , , , etc. can all match the part of the selector. You may come across older articles saying that :not() can only accept simple selectors, and if you need to target multiple items, you’d chain them like : not(.foo).not(.bar PERSON ) . This is outdated. The syntax has since been updated in version four CARDINAL of the selectors specification to accept a selector list, so you can now do : not(.foo ORG , .bar) .

can only accept simple selectors, and if you need to target multiple items, you’d chain them like . This is outdated. The syntax has since been updated in version four CARDINAL of the selectors specification to accept a selector list, so you can now do . You can test for support of the new selectors by using the selector() function in a @supports rule, like so: @supports selector(:is(*)) . Although the selector() function is relatively new itself, it has excellent support.

Permalink to Nesting Selector Nesting Selector ORG

Okay, let’s talk about the reason I went down this rabbit hole at all — the & nesting selector! This new selector was added for CSS Nesting ORG (though it can be used in other contexts, such as CSS Scoping). When used in CSS Nesting ORG , the & will be replaced with the parent style rule’s selector, with the same specificity as if they were wrapped with :is() .

a { & :hover { color : rebeccapurple PERSON ; } } :is(a):hover { color: rebeccapurple; } Code language: SCSS PERSON ( scss GPE )

In most cases this won’t make a difference, it’s just interesting to note, but it could produce some unwanted side effects.

#card , .card { .featured & { color : cornflowerblue; } } .featured :is( #ca rd MONEY , .card) { color: cornflowerblue; } Code language: SCSS PERSON ( scss GPE )

Since the & selector includes an ID, the resulting rule will use the specificity of the ID. So be aware of the potential for unexpected specificity conflicts when using & .

It’s also worth noting that since the nesting selector behaves like the :is() selector, it also can’t represent pseudo-elements.

a , a ::before , a ::after { color : red; & :hover { color : blue; } } a , a ::before , a ::after { color : red; } a :hover { color : blue; } Code language: SCSS PERSON ( scss GPE )

As you can see, the pseudo-elements are ignored in the hover state in the final rule.

Permalink to Conclusion Conclusion

Connecting to blog.lzomedia.com... Connected... Page load complete