DEV Community

Cover image for CSS: select first-of-class with the subsequent sibling combinator
Phil Nash
Phil Nash

Posted on • Originally published at philna.sh on

CSS: select first-of-class with the subsequent sibling combinator

There are a whole bunch of CSS selectors available to web developers, but sometimes there's still not enough. I found this recently when building the speaking section of my site and wanted to use the non-existent :first-of-class pseudo class to apply some styles.

The problem

The pseudo class :first-of-type does exist, but it is limited. It is a special case of the :nth-of-type() pseudo class. From the spec (emphasis mine):

The :nth-of-type(An+B) pseudo-class notation represents the same elements that would be matched by :nth-child(|An+B| of S), where S is a type selector and namespace prefix matching the element in question.

That means, when using :nth-of-type() or :first-of-type the selector can only be a type selector. That is, you can only refer directly to elements—like <p> or <h1>—when using :first-of-type. So, if you had the following HTML:

<div>
  <p>This is paragraph 1</p>
  <p class="special">This is paragraph 2</p>
  <p class="special">This is paragraph 3</p>
</div>

And this CSS:

div p { color: #333; }
div p:first-of-type { color: red; }
div p.special:first-of-type { color: green; }

You might expect paragraph 1 to be coloured red and paragraph 2 to be coloured green. But div p.special is not a type selector, so :first-of-type doesn't apply here and nothing is green.

The subsequent sibling combinator

This can be solved with a technique created by Daniel Tan and Lea Verou and shared on Stack Overflow. Rather than using pseudo classes at all, we can use the subsequent or general sibling combinator.

The subsequent sibling combinator looks like A ~ B where A and B are two compound selectors (not just type selectors). It allows you to select B where A and B share a parent and A precedes B in the document.

With the following HTML, the selector h2 ~ p would select the second <p> as it follows the <h2> but not the first <p>.

<div>
  <h1>Heading</h1>
  <p>Meta data</p>
  <h2>Subheading</h2>
  <p>Some article text</p>
</div>

The subsequent sibling combinator and :first-of-class

The trick to using the combinator to emulate a :first-of-class psuedo class is to use a regular selector to style all the elements of the class with the style you want. Then use the combinator to turn it off for all but the first element. In our original example, the CSS now looks like this:

div p { color: #333; }
div p:first-of-type { color: red; }
div p.special { color: green; }
div p.special ~ p.special { color: #333; }

Now the first paragraph will be red, the second green and the last one grey. You can check out a live example of this on Codepen.

CSS Selectors level 4

After posting this, Šime Vidas pointed out on Twitter that the future of CSS holds more promise for this style of selector. In the CSS Selectors level 4 specification the :nth-child() pseudo class takes an argument that looks like: An+B [of S]?. The An+B part means you can provide a function to calculate what n is, but the optional of S means the pseudo class will match the Nth element that matches the selector.

This means we can update our example to use :nth-child() instead of two rules like we did above. Check out the CSS below:

div p { color: #333; }
div p:first-of-type { color: red; }
div :nth-child(1 of p.special) { color: green; }

Using :nth-child(1 of p.special) means we are selecting the first child of the <div> that is a <p> with a class of "special". This is exactly what I wanted.

The only drawback with this technique? It only works in Safari right now. I've updated the Codepen, check it out in Safari to see all your future selector dreams come true.

CSS hacking is still fun

I've been writing CSS on and off for more than a decade now and while all the modern layout capabilities of CSS mean that there's a lot less hacking, sometimes you need to come up with a creative solution to a problem. I could have solved this with an extra class or a rearrangement of my HTML, but when CSS can do the job for you it feels more satisfying.

Thanks to Daniel and Lea for sharing their solutions on Stack Overflow and particularly to Daniel who's answer goes into a lot more detail about CSS selector and pseudo class misunderstandings.

Maybe one day we'll see a :first-of-class pseudo class. With the latest version of :nth-child() maybe we don't even need a :first-of-class, we're just waiting for browser support. In the meantime the subsequent sibling combinator remains our friend.


CSS: select first-of-class with the subsequent sibling combinator was originally published at philna.sh on Mar 18, 2018.

Top comments (9)

Collapse
 
equinusocio profile image
Mattia Astorino • Edited

Hi, after some test, i found an alternative solution (i think)

codepen.io/equinusocio/pen/NYpegd

<div class="cont">
    <div class="box">box</div>
    <div class="box">box</div>
    <div class="box box--special">box</div>
    <div class="box box--special">box</div>
    <div class="box box--special">box</div>
    <div class="box box--special">box</div>
    <div class="box box--special">box</div>
    <div class="box box--special">box</div>
</div>
.box {
    width: 100px;
    height: 100px;
    padding: 8px;
    background-color: yellowgreen;
    margin: 10px;
}

.box--special {
    border: 1px solid #000;
    background-color: #fff;
}

.box--special + .box--special:not(:last-child) {
    background-color: red;
    color: #FFF;
}

Collapse
 
philnash profile image
Phil Nash

That doesn’t do the same, notably the + combinator is the direct sibling, so if you throw another <div> in the middle with a different class it breaks.

Collapse
 
equinusocio profile image
Mattia Astorino • Edited

Yes i know. But both solutions have caveats since even with the subsequent sibling the code will break if you use another element type (because you are bound to type of the element).

BTW, nice exploration, thank you :)

Collapse
 
equinusocio profile image
Mattia Astorino • Edited

Nice! We now have to wait the full support.

Collapse
 
philnash profile image
Phil Nash

Well, full support for the latest :nth-child(). You can definitely use the subsequent sibling selector for now.

Collapse
 
equinusocio profile image
Mattia Astorino

I mean lv 4 selector :)

Thread Thread
 
philnash profile image
Phil Nash

I can’t wait either!

Collapse
 
chrismitchell profile image
Chris Mitchell

Great post Phil! This would have been perfect timing if released last week but none the less an great piece.

Collapse
 
philnash profile image
Phil Nash

Replied to you on Twitter too, but wanted to re-iterate. I wish I written this a week (or more) ago too. It's been in my drafts for ages.

Glad you did sort it out yourself though!