Parent Selectors Without JavaScript

CSS has historically been limited in one particular way: there was no mechanism to style an element based on what it contained or what came after it. That forced developers into JavaScript workarounds for relatively simple layout and styling decisions. The :has() pseudo-class changes that. It lets a stylesheet target an element by checking for a descendant or a following sibling, effectively delivering the parent selector that CSS practitioners have long requested.

Think of :has() as a conditional style: if the target element contains (or is followed by) a specific match, the style applies to the target. The syntax is straightforward.

:has(<direct-selector>) {
  /* ... */
}

“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.”

For a more robust explanation, MDN does it perfectly

A Common Styling Problem

Consider a blog listing page where an h1 post title is occasionally followed directly by an h2 subheading. When that subheading is present, you might want the title styled differently to signal that the article is more substantial. Without CSS support for parent or previous-sibling selection, the only practical approach was JavaScript.

Here is the old way: a script finds every h1 that has an h2 immediately after it and toggles a class accordingly.

const h1Elements = document.querySelectorAll('h1');

h1Elements.forEach((h1) => {
  const h2Sibling = h1.nextElementSibling;
  if (h2Sibling && h2Sibling.tagName.toLowerCase() === 'h2') {
    h1.classList.add('highlight-content');
  }
});

The modern equivalent drops the JavaScript entirely. One CSS rule checks each h1 for a following h2 sibling and applies the same visual change directly.

h1:has(+ h2) {
    color: blue;
}

The result is cleaner markup, no script, and styling logic that lives alongside the rest of the stylesheet.

Practical Use Cases

Making a Parent Card Stand Out

A frequent task in UI work is highlighting a container when it meets a particular structural condition — for instance, a product card that contains a sale badge. The markup stays semantic; the style is applied conditionally.

HTML

<h1>Lorem, ipsum dolor.</h1>
<h2>Lorem ipsum dolor sit amet.</h2>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eius, odio voluptatibus est vero iste ad?</p>
	
<!-- WITHOUT HAS BELOW -->

<h1>This is a test</h1>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eius, odio voluptatibus est vero iste ad?</p>

CSS

h1:has(+ h2) {
    color: blue;
}
Example of showing :has being used with pseudo classes.

Styling Figures with Captions

Images are another area where :has() shines. A figure element may include a figcaption with caption text (distinct from the alt attribute on the image). A caption changes the visual weight of the figure, and :has() can reflect that.

HTML

<section>
  <figure>
    <img src="https://placedog.net/500/280" alt="My aunt sally's dog is a golden retreiver." />
    <figcaption>My Aunt Sally's Doggo</figcaption>
  </figure>
</section>

CSS

figure:has(figcaption) {
  background: #c3baba;
  padding: 0.6rem;
  max-width: 50%;
  border-radius: 5px;
}
Example of :has selector highlighting background an image with an caption vs one that does not.

Notes and Caveats

:has() is well supported across current browser releases. The most important limitations from the CSS spec, summarized from MDN:

  • The specificity of a :has() rule equals the specificity of the most specific selector inside its argument.
  • In browsers that do not support :has(), the entire selector block fails unless :has() is placed inside a forgiving selector list such as :is() or :where().
  • :has() cannot be nested directly inside another :has().
  • Pseudo-elements are not valid both as arguments to :has() and as the anchor element for the pseudo-class.

Learning and Reference Materials

If you want to go further with :has(), the following resources are useful starting points:

  • Article by Ahmad Shadeed on the parent selector approach
  • CSS-Tricks almanac entry for the has selector
  • Chrome Developer Blog post on :has() in Chrome 105 with form-related patterns
  • CSS-Tricks deep dive on the :has() selector