Search code examples
csscss-selectors

Can complex selectors be used inside the `:where()` or `:is()` pseudo class functions?


I am using JS's querySelectorAll() to get some elements. The following selector works as I want: main > div, main > section, main > ul > li.

I would like to remove the main > redundancy part of that. The following works for me: main > :where(div, section), main > ul > li.

But, I cannot figure out how to get the ul > li complex selector portion to work inside there :where() function. It is just ignored. The div and section are picked up, just like a forgiving-selector-list should.

From what I can see, div, section, ul > li is a valid selector list.

However, in my research, I have only seen uses of :where() or :is() with simple selectors, maybe compound.

What am I missing?

If it matters, I am using Firefox 117.0.1


This snippet works using separate selectors:

main > div,
main > section,
main > ul > li {
    color: red;
}
<main>

   <div>Div</div>
   
   <section>Section</section>
   
   <ul>
    <li>List item 1</li>
    <li>List item 2</li>
   </ul>

</main>


This snippet works using :where() for div, section:

main > :where(div, section),
main > ul > li {
    color: red;
}
<main>

   <div>Div</div>
   
   <section>Section</section>
   
   <ul>
    <li>List item 1</li>
    <li>List item 2</li>
   </ul>

</main>


This snippet doesn't work using :where() for div, section and ul > li:

main > :where(div, section, ul > li) {
    color: red;
}
<main>

   <div>Div</div>
   
   <section>Section</section>
   
   <ul>
    <li>List item 1</li>
    <li>List item 2</li>
   </ul>

</main>


Solution

  • The problem isn't to-do with using a complex-selector with :where() and :is() - or anything to do with them using a forgiving-selector-list; the problem is that main > :where(ul > li) is not satisfiable.

    The current (draft) specification for CSS Selectors Level 4, defines :where() as being identical to :is() but with a specificity of zero, so let's take a look at :is() instead:

    https://drafts.csswg.org/selectors/#matches

    the [:is()] pseudo-class matches any element that matches any of the selectors in the list.

    ...therefore, main > :where(ul > li) matches elements that simultaneously satisfy:

    • Is an immediate child of main
    • Is an li that is also an immediate child of ul
    • But an element cannot be an immediate child of two different parent elements.
    • Therefore the selector cannot be satisfied.

    I would like to remove the main > redundancy part of that. The following works for me:

    main > :where(div, section),
    main > ul > li
    

    The problem here is that you think that's what you ought to do. I don't see any "redundancy" in your selectors. While this might superficially look like a violation of DRY, in my opinion it is not; or summarized as: you cannot simplify your selector any further.


    The snippet below demonstrates that you can use a complex-selector inside :where() or :is(), provided the selector is still satisfiable, in this case, by using the descendant combinator (a space) instead of the child combinator >, like so:

    main :where(ul > li, div)
    

    This works because an element e that matches ul > li can also match main e (but not main > e). But this selector also matches other ul > li descendants of main, such as List item 3 below:

    ul { color: blue; }
    
    main :where(ul > li, div) {
        color: red;
    }
    <main>
    
       <div>Div (red)</div>
       
       <section>Section (black)</section>
       
       <ul>
        <li>List item 1 (red)</li>
        <li>List item 2 (red)</li>
       </ul>
    
       <fieldset>
           List in a fieldset
           <ul>
            <li>List item 3 (red)</li>
            <li>List item 4 (red)</li>
           </ul>
       </fieldset>
    
    
    </main>