Day 91: a previous sibling selector with :has()

posted on

It’s time to get me up to speed with modern CSS. There’s so much new in CSS that I know too little about. To change that I’ve started #100DaysOfMoreOrLessModernCSS. Why more or less modern CSS? Because some topics will be about cutting-edge features, while other stuff has been around for quite a while already, but I just have little to no experience with it.


I’ve already shown much appreciation for the :has() pseudo-class in this series, but that we can use it as a previous sibling selector tops it all of.

Since this is not an official selector, but more something like a hack, it can be hard to read and interpret. So, let’s start nice and easy.

We have three buttons. If we hover/focus one button and we want to highlight it and the next adjacent button in the DOM at the same time, we can use the adjacent sibling selector.

  button {
    outline-width: 8px;
    outline-offset: 4px;
    outline-color: hotpink;
  }

  button:is(:hover, :focus-visible) {
    outline-style: solid;
  }

  button:is(:hover, :focus-visible) + button {
    outline-style: dashed;
  }

There's no previous item selector, but using :has() we can select an item that comes before another item. I've written about next-sibling combinators and :has() on day 26. Here’s an example from that post: The following code sets the block-end margin of all <h2> to 0 if they're followed by a <time> element.

h2 {
  margin-block-end: 0.7em; 
}

h2:has(+ time) {
  margin-block-end: 0;
}
<h2>Heading</h2>
<p>Teaser text</p>

<h2>Heading</h2>
<time>31.10.2022</time>
<p>Teaser text</p>

Heading

Teaser text

Heading

Teaser text

If we want to use this in our button example, we have to select a <button> followed by a <button> in the :hover or :focus-visible state.

  button:is(:hover, :focus-visible) {
    outline-style: solid;
  }

  button:is(:hover, :focus-visible) + button {
    outline-style: dashed;
  }

  button:has(+ button:is(:hover, :focus-visible)) {
    outline-style: dotted;
  }

If we have more buttons and we want to select the button that comes before the previous button, we can extend our selector.

  button:has(+ button + button:is(:hover, :focus-visible)) {
    /* styles */
  }

What a beauty!

Here are a couple of other demos:

See the Pen Day 91: previous sibling selector with :has() #100DaysOfMoreOrLessModernCSS by Manuel Matuzovic (@matuzo) on CodePen.

See the Pen selecting previous item using CSS by pourya (@pouriversal) on CodePen.

See the Pen BUBBLE (previous siblings!) by Chris Coyier (@chriscoyier) on CodePen.

See on CodePen

Further reading

Do you want to learn even more awesome CSS?

I'm running a workshop with my friends at Smashing Magazine in which I introduce you to the most useful modern features in CSS and show how you can implement them today in your code base to improve scalability, maintainability, and productivity.

Learn more about the workshop!

Overview: 100 Days Of More Or Less Modern CSS