How to Select Parent Elements with CSS :has()

The CSS :has() pseudo-class functions as a relational selector that allows developers to style an element based on its descendants or subsequent siblings. Historically referred to as the "parent selector," :has() checks whether an element contains or is followed by elements that match a specified selector list, enabling conditional parent styling purely in CSS without relying on JavaScript event listeners or DOM mutations.

The Mechanism of :has()

CSS traditionally cascades downwards, styling children based on their parents or ancestors. The :has() pseudo-class reverses this dynamic by accepting a relative selector list as an argument. The browser evaluates whether the element matching the base selector contains children or descendants that satisfy the condition inside the parentheses.

The basic syntax is:

target-element:has(condition) {
  /* styles applied to target-element */
}

If the condition inside :has() matches at least one descendant, the base element receives the applied styles. If the condition is false, the rule is ignored.

Selecting Parents Based on Child State

The true utility of :has() lies in its ability to respond to dynamic user states, such as focus, selection, and form validation. Because pseudo-classes like :checked, :focus, :hover, and :disabled update dynamically in the DOM, :has() recalculates instantly when a child element changes state.

1. Form Element States (Checkbox and Radio)

Instead of using JavaScript to toggle an active class on a container when a user toggles an input, :has() targets the container directly:

/* Styles the card container when an input inside it is checked */
.card:has(input[type="checkbox"]:checked) {
  border-color: #0066cc;
  background-color: #f0f7ff;
}

2. Input Validation States

A parent form or field wrapper can indicate an error or success state without needing external wrapper classes:

/* Highlights the input group if any child input fails validation */
.form-group:has(input:invalid:not(:placeholder-shown)) {
  border-left: 4px solid #d9534f;
}

3. Focus-Within and Interactive Contexts

While :focus-within allows styling when any descendant has focus, :has() offers granular control by targeting specific focus types or specific focused children:

/* Styles a navigation menu when a specific dropdown link receives focus */
.nav-item:has(.submenu a:focus-visible) {
  background-color: #e0e0e0;
}

Using Combinators Inside :has()

The argument passed to :has() can utilize standard CSS combinators to restrict the relationship to direct children or specific sibling patterns:

  • Child Combinator (>): Matches only if the condition applies to a direct child, rather than a deeply nested descendant:

    .container:has(> .alert) {
      padding-top: 2rem;
    }
  • Adjacent Sibling Combinator (+): Evaluates whether an element is immediately followed by a matching sibling:

    h2:has(+ p.intro) {
      margin-bottom: 0.5rem;
    }

Performance and Implementation

Modern browser rendering engines optimize :has() by scoping invalidations locally rather than triggering full-document recalculations. However, broad selectors such as *:has(*) or chaining multiple relational selectors across complex DOM trees can lead to increased style recalculation overhead. To maintain optimal performance, keep the subject and the relative selector as specific as possible.