Using the CSS :has Pseudo‑Class

Abstract image used to represent Using the CSS :has Pseudo‑Class
Image by Tim Mossholder.

The :has() relational pseudoclass is part of Selectors Level 4. It lets us match an element based on a relationship to other elements, including the parentandchild cases in this article. That gives us a way to express some conditions that previously needed a class added from JavaScript.

Using :has, we can resolve a number of common frontend scenarios, such as when you want to style a container based on the contents of one of its child elements. However, it is important to understand the limitations and ideal use cases of this selector before using it in your own projects.

At publication in May 2022, :has() was defined in the Selectors Level 4 working draft, but stable support was narrow. Safari 15.4 had shipped it in March. Chrome and Edge had not yet enabled it by default, and Firefox support arrived later. A fallback was therefore necessary for sites whose browser range extended beyond Safari 15.4.

Chrome and Edge were Chromium browsers using Blink by then, not WebKit. EdgeHTML had already been retired from the stable Edge channel, so it was not part of the support picture for this article. Chrome 105 and Edge 105 added :has() later in 2022, followed by Firefox 121 in December 2023. Check a current compatibility source before using it without a fallback.


Using :has

With the more stark compatibility issues out of the way, let's talk about actually using this relational pseudoclass.

Usage should be very familiar; simply include the :has function followed by the child selector you want to match. For example:

div:has(p) {
  margin-bottom: 1rem;
}

What this will do is match any div with a p child, and set the bottom margin to 1rem. This falls back to more generic CSS selectors, so in the example above, both of these parent divs will match:

<!-- This div will match the selector above -->
<div>
  <p>Lorem ipsum dollar</p>
</div>
<!-- This div will also match the selector above -->
<div>
  <article>
    <p>Lorem ipsum dollar</p>
  </article>
</div>
<!-- This div will also match the selector above -->
<div>
  <!-- ..and so will this one -->
  <div>
    <p>Lorem ipsum dollar</p>
  </div>
</div>

You can of course use more complex selectors like child selectors (>). For example:

div:has(> p) {
  margin-bottom: 1rem;
}

This will only match divs that have an immediate p child:

<!-- This div will match the selector above -->
<div>
  <p>Lorem ipsum dollar</p>
</div>
<!-- This div will not match the selector above -->
<div>
  <article>
    <p>Lorem ipsum dollar</p>
  </article>
</div>

Start with the relationship you actually need to match, such as a descendant or direct child. More elaborate combinations still have to follow the selector grammar and work in the browsers you support.

What you can also do is chain multiple :has selectors together (in the same way you can with pseudoclasses), which can create more explicit matches. For example, you could select a div element that has both a p element and a img element as children like this:

div:has(p):has(img) {
  padding: 10rem;
}

A selector list inside one :has() expresses a different condition. The following matches a div that has a p or an img (or both), whereas the chained form above requires both conditions:

div:has(p, img) {
  padding: 10rem;
}

The Wrap‑up

To summarise, if you aren't put off by the current level of browser support, then :has is a very powerful feature that can help you to create more complex and dynamic styles for your website, and write more elegant CSS.

It is, however, important to understand its limitations and use it in appropriate scenarios. :has() is a relational selector, and a very broad match can give the browser more style invalidation and selector work when the DOM changes. Engines include optimisations for it, so keep the selector as narrow as the component allows and measure the actual page instead of assuming every use will be expensive.

Postscript

June 2026: :has() is no longer just an upcoming selector. It is usable in modern browser support matrices, but the advice to use it deliberately still stands. The current selector rules do not allow a :has() inside another :has(), and pseudoelements are excluded unless a specification explicitly allows them. I would now frame the practical question as whether the selector keeps the component state readable and cheap enough for the page it sits on.

Have a complex web platform issue?

Tell me what is blocked, what has changed, and what needs to be true after the fix. I'll come back with a practical next step.