:has() Selector
Overview
For 30 years, CSS could only look 'down' the DOM tree (styling a child based on its parent). The :has() selector is arguably the most revolutionary addition to CSS since Flexbox. It is the 'Parent Selector'. It allows you to style a parent container conditionally, ONLY if it contains a specific child. This entirely eliminates the need for JavaScript in hundreds of common UI patterns (like highlighting a card if a checkbox inside it is checked).
Syntax
/* 1. The Parent Selector */
/* Style the entire Card ONLY if it contains an <img> tag */
.card:has(img) {
border: 2px solid blue;
}
/* 2. Interactive States */
/* Highlight the Card if the user focuses on an input inside it! */
.card:has(input:focus) {
box-shadow: 0 0 10px gold;
}
/* Style the Card if a checkbox inside is checked! */
.card:has(input[type="checkbox"]:checked) {
background-color: lightgreen;
}
/* 3. The "Previous Sibling" Hack */
/* Style an H2 ONLY if it is immediately followed by a paragraph */
h2:has(+ p) {
margin-bottom: 0;
}Common Pitfalls
- Overusing
:has()globally on the<body>or<html>tag. Because:has()requires the browser to look downwards at every single child node before rendering the parent, attaching it to the root of a massive DOM tree can cause severe performance and rendering lag. - Assuming it only checks direct children.
:has(img)will trigger if there is animg50 levels deep. If you only want to check direct children, you must use the child combinator::has(> img).
Interview Questions
:has() selector historically considered impossible for browser engineers to implement?Because of the CSS rendering pipeline. Browsers render elements top-down. To style a parent based on a child, the browser would have to halt rendering, traverse down to find the child, and then traverse back up to repaint the parent, causing massive layout thrashing. Modern optimized engines finally solved this.
Real-World Example
Building an interactive Navigation Menu that dims the rest of the links when you hover over one link, using pure CSS.
/* If the user is hovering over ANY link inside the nav... */
nav:has(a:hover) a:not(:hover) {
/* Dim all the links that are NOT currently being hovered! */
opacity: 0.5;
filter: blur(2px);
transition: all 0.3s ease;
}Check Your Knowledge
Test your understanding of :has() Selector with these quick questions.