CSS Fundamentals
CSS :has() — The Parent Selector That Changes Everything
CSS :has() is the selector developers have wanted for 20 years. Learn how to style parents based on their children, create conditional layouts and reduce your JavaScript dependency.
For two decades, CSS had no way to style a parent based on its children. You wanted to highlight a form when it contained an invalid input? JavaScript. :has() changes that.
Basic syntax
:has() matches an element if any of its descendants match the selector inside:
/* Style a form that contains an invalid input */
form:has(input:invalid) {
border-color: #e07070;
}
/* Style a card that contains an image */
.card:has(img) {
padding: 0;
}
/* Style a nav that contains the active link */
nav:has(a.active) {
background: var(--accent-dim);
}
Conditional layouts
One of the most powerful use cases is changing layout based on content:
/* Grid when there are 3+ items, flex for fewer */
.grid:has(:nth-child(3)) {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
/* Full-width image card vs text-only card */
.card:has(img) {
grid-column: span 2;
}
.card:not(:has(img)) {
background: var(--bg-card);
}
Form state styling without JavaScript
/* Highlight label when its input is focused */
.field:has(input:focus) label {
color: var(--accent);
}
/* Show error message when field is invalid and dirty */
.field:has(input:invalid:not(:placeholder-shown)) .error-msg {
display: block;
}
/* Style submit button based on form validity */
form:has(input:invalid) button[type="submit"] {
opacity: 0.5;
pointer-events: none;
}
Browser support
:has() has excellent support as of 2024 — Chrome 105+, Safari 15.4+, Firefox 121+. Global browser support is above 90%. It's production-ready.
@supports selector(:has(a)) {
/* Safe to use :has() here */
nav:has(.active) { background: var(--accent-dim); }
}