↔️ Sideway selection in CSS with :has()
Tags: webdev, frontend, css
Published: 2024-01-31 11:40:14

Hi folks! 👋 Today I would like to share with you this **codepen** I created in order to showcase a simple but really **cool use** for the recently introduced `:has()` **selector**.

{% codepen https://codepen.io/francescovetere/pen/abMJgMz %}

If you hover with your mouse over any of the emojis, you'll notice that not only the **hovered emoji** smoothly pops up, but its **previous and next siblings** also get affected a little bit, creating a very pleasant effect.

This cool effect is only possible thanks to the `:has()` **selector**, recently introduced in the CSS world and [now available for all the major browsers](https://caniuse.com/css-has).

➡️ `:has()` is a **functional pseudo-class** that allows us to style an element based on its descendants or any succeeding elements.

- Basically, `:has()` allows to style the **element it is attached to** — otherwise known as the **target** element. This is **similar to other pseudo-classes** like `:hover`, where `a:hover` is intended to style the `<a>` element in an hovered state.

- However, `:has()` is also similar to `:is()`, `:where()`, and `:not()`, in that it accepts a a **list of relative selectors** within its parentheses. This allows `:has()` to create **complex criteria to test against**, making it a very powerful selector.

For example, if I wanted to select all the `<article>` elements that contain an `<img>` as child, `:has()` would make this quite a simple task:

```css
article:has(img) {
  /* ... */
}
```

This use of `:has()` is certainly one of the most common: it basically acts as a **parent selector**, something that **really was missing** in the web platform and was highly demanded for years by developers.

❗ **BUT**, because `:has()` actually accepts a relative selector list as its argument, you can select **so much more** than just the parent element! Using various CSS combinators, it is now possible to **not only go up** ⬆️ the DOM tree, but **also** do **sideway selections!** ↔️

For example, `article:has(+ article)` will select any `<article>` element that has another `<article>` as a direct sibling. This apparently simple selection would just not be possible without `:has()`, and it would have required some extra JavaScript.

💡 The codepen I realized takes advantage of this very idea. I basically want to **apply some styling** (a scaling and a translation) for the **currently hovered emoji**, but **also** for its **previous and next sibling.**

➡️ `:has()` makes this really easy:

```css
.dock li:hover {
  /* scale and translate the hovered emoji */
}

.dock li:hover + li {
  /* scale and translate the next emoji */
}

.dock li:has(+ li:hover) {
  /* scale and translate the previous emoji */
}
```

Of course the full code is a little bit more complex than that (you can check out the [codepen](https://codepen.io/francescovetere/pen/abMJgMz) if you're curious), but the **main idea** really is based on this simple snippet. Having a way to select not only an element and its next sibling, but also its previous one, opens up a whole new world of possibilities.

<hr>

And that's it! Feel free to leave a comment and let me know what other cool stuff you built using `:has()` 😉

Till next time! 👋