For as long as CSS has existed, selectors could only look "downward" or "sideways" — you could style an element based on its children or its siblings, but never style a parent based on what's inside it. The :has() pseudo-class finally breaks that rule. It's often nicknamed the "parent selector," and it unlocks styling patterns that previously required JavaScript.

How :has() Works

:has() takes a selector as its argument and matches any element that contains at least one descendant matching that selector. You attach it directly to the element you actually want to style — the parent — not the element inside it.

/* Matches a .card that contains an img anywhere inside it */
.card:has(img) {
  border: 1px solid #ddd;
}

/* Matches a .card that does NOT contain an img */
.card:not(:has(img)) {
  background: #f3f4f6;
}

Cards with an image inside them get a border; cards without one get a gray background instead — all decided purely by what's nested inside the card, with no JavaScript checking the DOM.

Browser Support

As of 2026, :has() is supported in every major modern browser (Chrome, Edge, Safari, and Firefox). It's safe to use for new projects, though if you need to support genuinely ancient browsers, treat any :has()-based styling as a progressive enhancement rather than something layout-critical depends on.

A Practical Form Validation Example

<div class="form-group">
  <label for="email">Email</label>
  <input type="email" id="email" required />
</div>
.form-group:has(input:invalid) {
  border-left: 3px solid #dc2626;
  background: #fef2f2;
}

.form-group:has(input:valid) {
  border-left: 3px solid #16a34a;
}

As soon as the visitor types an invalid email, the entire .form-group wrapper — the label and the input together — picks up a red left border and a light red background, purely from CSS reacting to the input's built-in :valid/:invalid state. Before :has(), styling a parent based on its child's validity state required a JavaScript event listener toggling a class.

Another Common Pattern: Sibling-Aware Layouts

/* Give a container extra padding only when it has more than 3 children */
.list:has(> :nth-child(4)) {
  padding: 2rem;
}

:has() combines naturally with the other CSS combinators and pseudo-classes covered elsewhere in this course — here the child combinator (>) combined with :nth-child() checks whether a fourth item exists at all, applying extra spacing only to lists long enough to need it.