The Long-Awaited Parent Selector
For decades, web developers have asked for a way to style a parent element based on its children using only CSS. We resorted to complex JavaScript MutationObservers or utility classes like .is-active toggled via event listeners. With the widespread browser support of the :has() pseudo-class, that era is officially over. Often called the "relational pseudo-class," :has() allows you to select an element if it contains a specific descendant or if a selector matches relative to that element.
Dynamic Card Layouts Without Conditional Logic
Consider a standard card component. You might want to change the grid layout or background color only if the card contains a featured image. Previously, your template logic would need to add a .card--has-image class. Now, CSS handles it natively.
/* Style the card only if it contains an <img> */
.card:has(img) {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 1rem;
align-items: center;
}
/* Apply a border only if the card has a specific header */
.card:has(h2.featured-title) {
border: 2px solid #ffd700;
}
Interactive Form Validation
One of the most powerful use cases for :has() is form UX. You can style a fieldset or a label based on the validity of the input inside it. This creates a much more responsive feel without writing a single line of validation script for the UI state.
/* Highlight the fieldset if any input inside is invalid and dirty */
fieldset:has(input:invalid:not(:placeholder-shown)) {
border-color: #ff4d4d;
background-color: #fff5f5;
}
/* Add a red asterisk to the label if the input is required */
label:has(+ input[required])::after {
content: " *";
color: red;
}
The "Dark Mode" and UI Toggle Hack
You can even use :has() to manage global states. By checking the state of a hidden checkbox, you can theme your entire application. This effectively turns a checkbox into a state manager for its parent or siblings.
/* Change the body background if the theme toggle is checked */
body:has(#theme-switch:checked) {
background-color: #1a1a1a;
color: #f0f0f0;
}
/* Adjust navigation when a mobile menu checkbox is active */
header:has(.menu-toggle:checked) .nav-links {
display: block;
position: absolute;
top: 100%;
}
Performance and Browser Support
While :has() was initially feared for potential performance hits (as the browser must look "down" and then "up" the DOM tree), modern engines have optimized this significantly. As of 2024, it is supported in all major evergreen browsers including Chrome, Firefox, and Safari. However, avoid nesting extremely complex selectors inside :has() within large loops to keep your frames smooth. Stick to direct children or simple attributes for the best results.

