Reference guide · html-css-js · Published 2026-08-16 · 3 min read

The CSS :has() selector guide

Use the CSS :has() selector to style a parent from its children and cut JavaScript class toggling, without hurting performance.

The CSS :has() selector, often called the parent selector, lets you style an element based on whether it contains a certain descendant. It inverts the normal direction of CSS: instead of selecting a child from its ancestor, you select an ancestor because of what is inside it. Since it reached baseline widely available support, it has become a practical way to replace the class-toggling JavaScript that used to be needed for content-based styling.

What it does

Written from the element you want to style, :has() takes a selector list and matches if the element has any matching descendant.

.card:has(img) {
  border: 1px solid #ddd;
}

form:has(input:invalid) {
  outline-color: #d33;
}

The first rule styles only cards that contain an image. The second styles a form while one of its inputs is invalid, which is the kind of state styling that previously required a script to add and remove a class.

You can also scope the argument to a direct child or a specific position. .card:has(> .media) and .list:has(:nth-child(3)) narrow the match in a way that reads clearly and keeps the selector cheap.

Common patterns

The highest-value uses are where a parent's appearance should depend on its content, a state that JavaScript controllers used to manage:

Because it is a selector like any other, it composes with the rest of the cascade: you can use it alongside specificity, cascade layers and custom properties. It does not create new elements or new pseudo-elements, so a :has() version of a pattern is usually no heavier than the class-based version it replaces.

Performance notes

:has() has one performance caveat worth respecting. A complex parent selector re-evaluates when the DOM inside the document mutates, so a document-wide rule with a broad argument costs more than a narrowly scoped one. The guidance is to scope :has() to a component, such as .card:has(...) or #app :has(...), rather than to the document root, and to keep the argument as specific as you reasonably can.

In practice the vast majority of stylesheets will not hit a problem. The selector is baseline widely available, which means all current engines have shipped and stabilised it, and the performance concern is about pathological document-wide rules, not normal use. If you are styling hundreds of cards, a scoped component selector stays fast.

When :has() is doing work that used to live in JavaScript, you often also simplify the accompanying script. See css specificity to understand how its specificity competes, cascade layers for ordering, and javascript event delegation for the interactions that still genuinely need a script.

Need a website built, fixed, optimised, migrated or replaced?

This technical resource is written by CSMBAC, a small design and development studio. If you would rather hand the problem to a professional, the website service page explains how we build enquiry-ready websites.

Explore website services