Search code examples
csscss-selectors

This CSS :is() Selector Isn't Working As I Think It Should


Why does the CSS selector .a ~ .b :is(body .b .c .d) match .d in the following simple HTML markup when surely the space between the first .b and :is() is a descendant combinator so the selector should be trying to select any .d that is a descendant of a .c that is a descendant of a .b that is a descendant of body which (due to the space between .b and :is) is a descendent of any .b that is a following sibling of a .a?

body is not a descendant of .b however .d IS selected and .d's background-colour is set to chartreuse.

I thought that the selector list inside :is() or :where() is relative if it starts with a combinator (e.g. +, >, ~) else it is given an implied descendant combinator.so body .b .c .d should be matched only if they are descendants of .b which they're not (.c .d are but not .b .c .d nor body .b .c .d)

<div class="a"></div>
<div class="b">
  <div class="c">
    <div class="d"></div>
    </div>
</div>
.a {--bc: red;}
.b {--bc: green;}
.c {--bc: blue;}
.d {--bc: orange;}

:is(.a,.b,.c,.d) {
  aspect-ratio:1;
  border: 3px solid var(--bc);
  margin: 20px;
  width: 100px;
}

.a ~ .b :is(body .b .c .d) /* this colours .d but why? */
/* .a ~ .b :is(.b .c .d) -- so does this */
{
  background-color: chartreuse;
}

I tried the above HTML and CSS and was expecting .d to not be selected however the fact that it is has pulled the "how I think CSS works" rug from under me.

Note: I know the :is isn't necessary with only one selector list, the CSS that originally started me investigating this does have multiple selector lists inside the :is() but first I need to understand what is going on with just this one before I can start adding more selectors.


Solution

  • Your understanding of :is() isn't 100% correct.

    .a ~ .b :is(body .b .c .d)
    

    means select any element that match the selector

    .a ~ .b *
    

    AND also match the selector

    body .b .c .d
    

    What is inside the :is() has no relation with the outside part and this is a common mistake that everyone make because you see the selector as:

    .a ~ .b :body .b .c .d
    

    But it's not.

    So you will select any .d element that is a descendant of .b and also a descendant of body .b .c


    Another example to see the difference:

    .a > .b > .c {
      /* I will select the .c element */
    }
    
    .a > :is(.b > .c) {
      /* I will select nothing! */
    }
    
    <div class="a">
     <div class="b">
      <div class="c">
       </div>
      </div>
    </div>
    

    Both selectors aren't the same. The second one means select any direct descendant of .a that is a .c element and a direct descendant of .b.

    A good reading for more detail: https://www.bram.us/2023/01/17/using-is-in-complex-selectors-selects-more-than-you-might-initially-think/