CSS :has() Pseudo-class Selector

CSS :has() Pseudo-class Selector

The :has() relational pseudo-class is officially recognized as a Parent Selector, and in the actual spec [Selectors Level 4], it is described as a relative selector that accepts “forgiving-relative-selector-list“. However, :has() is extremely capable as far as selectors go, and there’s plenty of proof that it is more than just a parent selector.

Syntax

The syntax of the :has() pseudo-class is relatively simple and easy to learn to get started, because its semantics are very clear and consistent with the rendering presentation, meaning that the so-and-so element will match if it matches the so-and-so selector.

As a simple example, the following function means that if the element has an <a> element inside the <img> element, the <a> element will match.

a:has(img) { 
  display: block; 
}

We can use this selector to easily distinguish whether it is a text link or an image link and set different CSS styles.

The :has() pseudo-class supports all CSS selectors. E.g.:

a:has(> img) { 
display: block; 
}

The above function indicates that a elements whose matching child element is an img element will be matched, while more distantly related descendant elements will not be considered.

Note that in the above code, the :has pseudo-class argument - > selector - is written directly at the top of the argument, rather than a:has(a > img). The :has() pseudo-class has an invisible :scope pseudo-class at the top of its argument, so it is prohibited to write a:has(a > img).

Similarly, you can use selectors like + or ~ to achieve the effect of a "front sibling selector", where the front element is selected based on the back sibling element. Like so:

h3:has(+ p) { 
font-size: 1.5rem; 
}

The above query indicates a match for an h3 element followed by an p element. The :has() pseudo-class also supports complex selectors and selector lists. E.g.:

article:has(h3, p) { 
   background-color: #f3f3f3;
}

The above query indicates that only h3 elements or p elements inside the article elements will match: as an or relationship, not a with relationship.

If you want the relationship to be with, i.e., both h3 elements and p elements to match, you can write it like so:

article:has(h3):has(p) { 
   background-color: #f3f3f3; 
}

The :has() pseudo-class can also be mixed with other pseudo-classes, such as :checked, :enabled, etc., or even with other logical pseudo-classes, such as :not. E.g.:

section:not(:has(h3)) {
  border: 1px solid yellow;
}

section:has(:not(h3)) {
  color: orange;
}

Note that the two queries above have different meanings.

The former selector means that section elements that do not contain h3 elements have a yellow border, while the latter means that section elements that contain elements that are not h3 elements are orange in color.

It is honestly confusing for everyone. But once you understand that not -> has means has not, it gets easier.

Examples

The :has() pseudo-class has been available for quite some time now (either under browser flags or with a polyfill), and the possibilities of what this parent selector will be able to do will have to be unraveled over time.

In the meantime, this section covers examples of creative ways in which :has() is already being utilized.

#1: Styling figcaption only if it is present

#2: Carousel gallery with scale and gap effect in pure CSS

#3: Creating a native color theme switcher

Browser Support

Data on support for the css-has feature across the major browsers from caniuse.com