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.