The functional :has()
CSS pseudo-class represents an element if any of the relative selectors that are passed as an argument match at least one element when anchored against this element. This pseudo-class presents a way of selecting a parent element or a previous sibling element with respect to a reference element by taking a relative selector list as an argument.
/* Selects an h1 heading with a paragraph element that immediately follows the h1 and applies the style to h1 */ h1:has(+ p) { margin-bottom: 0; }
The :has()
pseudo-class takes on the specificity of the most specific selector in its arguments the same way as :is()
and :not()
do.
Syntax
:has(<relative-selector-list>) { /* ... */ }
If the :has()
pseudo-class itself is not supported in a browser, the entire selector block will fail unless :has()
is in a forgiving selector list, such as in :is()
and :where()
).
The :has()
pseudo-class cannot be nested within another :has()
. This is because many pseudo-elements exist conditionally based on the styling of their ancestors and allowing these to be queried by :has()
can introduce cyclic querying.
Pseudo-elements are also not valid selectors within :has()
and pseudo-elements are not valid anchors for :has()
.
Examples
With the sibling combinator
The :has()
style declaration in the following example adjusts the spacing after <h1>
headings if they are immediately followed by an <h2>
heading.
HTML
<section> <article> <h1>Morning Times</h1> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p> </article> <article> <h1>Morning Times</h1> <h2>Delivering you news every morning</h2> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p> </article> </section>
CSS
h1, h2 { margin: 0 0 1rem 0; } h1:has(+ h2) { margin: 0 0 0.25rem 0; }
Result
This example shows two similar texts side-by-side for comparison – the left one with an H1
heading followed by a paragraph and the right one with an H1
heading followed by an H2
heading and then a paragraph. In the example on the right, :has()
helps to select the H1
element that is immediately followed by an H2
element (indicated by the adjacent sibling combinator+
) and the CSS rule reduces the spacing after such an H1
element. Without the :has()
pseudo-class, you cannot use CSS selectors to select a preceding sibling of a different type or a parent element.
With the :is() pseudo-class
This example builds on the previous example to show how to select multiple elements with :has()
.
HTML
<section> <article> <h1>Morning Times</h1> <h2>Delivering you news every morning</h2> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p> </article> <article> <h1>Morning Times</h1> <h2>Delivering you news every morning</h2> <h3>8:00 am</h3> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </p> </article> </section>
CSS
h1, h2, h3 { margin: 0 0 1rem 0; } :is(h1, h2, h3):has(+ :is(h2, h3, h4)) { margin: 0 0 0.25rem 0; }
Result
Here, the first :is()
pseudo-class is used to select any of the heading elements in the list. The second :is()
pseudo-class is used to pass a list of adjacent sibling selectors as an argument to :has()
. The :has()
pseudo-class helps to select any H1
, H2
, or H3
element that is immediately followed by (indicated by +
) an H2
, H3
, or H4
element and the CSS rule reduces the spacing after such H1
, H2
, or H3
elements.
This selector could have also been written as:
:is(h1, h2, h3):has(+ h2, + h3, + h4) { margin: 0 0 0.25rem 0; }
Logical operations
The :has()
relational selector can be used to check if one of the multiple features is true or if all the features are true.
By using comma-separated values inside the :has()
relational selector, you are checking to see if any of the parameters exist. x:has(a, b)
will style x
if descendant a
OR b
exists.
By chaining together multiple :has()
relational selectors together, you are checking to see if all of the parameters exist. x:has(a):has(b)
will style x
if descendant a
AND b
exist.
body:has(video, audio) { /* styles to apply if the content contains audio OR video */ } body:has(video):has(audio) { /* styles to apply if the content contains both audio AND video */ }
Analogy between :has() and regular expressions
Interestingly, we can relate some CSS :has()
constructs with the lookahead assertion in regular expressions because they both allow you to select elements (or strings in regular expressions) based on a condition without actually selecting the condition matching the element (or string) itself.
Positive lookahead (?=pattern)
In the regular expression abc(?=xyz)
, the string abc
is matched only if it is immediately followed by the string xyz
. As it is a lookahead operation, the xyz
is not included in the match.
The analogous construct in CSS would be .abc:has(+ .xyz)
: it selects the element .abc
only if there is an adjacent sibling .xyz
. The part :has(+ .xyz)
acts as a lookahead operation because the element .abc
is selected and not the element .xyz
.
Negative lookahead (?!pattern)
Similarly, for the negative lookahead case, in the regular expression abc(?!xyz)
, the string abc
is matched only if it is not followed by xyz
. The analogous CSS construct .abc:has(+ :not(.xyz))
doesn't select the element .abc
if the next element is .xyz
.