What :has() changed in CSS?

Roy van der Loo
Pon.Tech.Talk
Published in
3 min readDec 21, 2023

--

Released on 2023–12–19 in Firefox 🎉

The long-awaited :has() pseudo-class has finally landed in Firefox, meaning it’s now available in all the major browsers ready to be used in production. :has() is part of the CSS Selectors Level 4 specification, which represents the latest addition to the already big set of available CSS selectors. CSS Selectors Level 4 is a set of powerful new selectors and features to the CSS language, including :matches(), :not() and :is().

So, what’s so special about :has()? In simple terms, it lets you style elements in a new way. Usually, in CSS, you style an element based on what it is or where it is. With :has(), you can now style an element based on what’s inside of it. Meaning if an element contains a certain thing, like an image or a link, you can change the style of the outer element because of that.

The syntax

:has() can take a list of CSS selectors as its parameters

:has(<CSS selectors>) {
/* Add some fancy CSS */
}

To bring this to life, let’s dive into some practical examples that illustrate how :has() can be applied in various scenarios.

Parent has a specific element

Style a list item differently if it contains a link.

li:has(a) {
background-color: tomato;
padding: 0.5rem;
color: white;
}

This targets li elements that contain at least one element, applying a specific background color, padding and color, thereby visually differentiating list items that are links.

Let’s see it in action

CodePen example including HTML and CSS

Has either or both of the elements
Style a .card element if it contains either a video or a picture element or both.

.card:has(video, picture) {
border: 3px solid tomato;
padding: 0.5rem;
}

This CSS rule targets .card elements that contain at least one video or picture element. The presence of either or both of these elements triggers the styling.

Let’s see it in action

Parent has both elements

Style a card only if it contains both an image and a label.

.card:has(img):has(.label) {
border: 3px solid tomato;
}

The .card element will be styled with a border only if it contains both an img element and an element with the class .label

Let’s see it in action

CodePen example including HTML and CSS

Does Not Have the Element
Style a menu item if it does not have a submenu (indicated by the absence of a ul element).

nav li:not(:has(ul)) {
background-color: tomato;
padding: 0.5rem;
}

This rule targets li elements within a nav that do not contain a ul, applying the styling. It’s useful for differentiating between elements.

Let’s see it in action

CodePen example including HTML and CSS

Select previous element with adjacent sibling combinator

Typically, the adjacent sibling combinator is used to style an element that directly follows another. However, with a creative twist, we can actually use it to style a preceding list item.

li:has(+ .label) {
background-color: tomato;
}

In this example, an <li> element is styled if it is immediately followed by an element with the class .label.

Let’s see it in action

CodePen example including HTML and CSS

In summary, the new :has() feature in CSS is a game-changer. It let us style elements based on what's inside them. And the best part? You can mix :has() with other CSS, like :not(), :empty , :hover, :valid or :invalid, :checked and many more to make CSS even more powerful.

--

--