CSS developers, rejoice! The :has() selector is one of the most significant additions to CSS in years. For the first time, we can style an element based on the presence of its descendants, a capability developers have requested for over a decade.
Goodbye, JavaScript-Driven Styling:
For years, styling a parent based on its children required JavaScript: query the DOM, check for a condition, and toggle a class. The :has() selector brings this power directly to CSS, enabling relational selectors that respond to the structure of the DOM.
Link to section: 1. Style a Card Based on Its Content1. Style a Card Based on Its Content
/* Highlight a card that contains an image */
.card:has(img) {
padding: 0;
}
/* Add a border to a card that contains a featured badge */
.card:has(.featured-badge) {
border-color: gold;
}
Link to section: 2. Change Layout Based on Sibling Presence2. Change Layout Based on Sibling Presence
/* Make the header sticky only when the page has a sidebar */
body:has(.sidebar) header {
position: sticky;
top: 0;
}
Link to section: 3. Form Validation Styling3. Form Validation Styling
Style a form field's label based on whether the input is invalid:
/* Highlight the label when its associated input is invalid */
label:has(+ input:invalid) {
color: red;
}
/* Style a field group that contains an error message */
.form-group:has(.error-message) {
margin-bottom: 1.5rem;
}
Link to section: 4. Conditional Card Layouts4. Conditional Card Layouts
/* A grid item that contains a video takes full width */
.grid-item:has(video) {
grid-column: 1 / -1;
}
Link to section: 5. Dark Mode Based on Image Content5. Dark Mode Based on Image Content
/* Switch text color to white when a section has a dark image */
section:has(.dark-image) {
color: white;
background: black;
}
Link to section: 6. Empty State Styling6. Empty State Styling
/* Hide the container if it has no children */
.menu:not(:has(*)) {
display: none;
}
Link to section: 7. Hover-Based Sibling Styling7. Hover-Based Sibling Styling
/* Dim siblings when one card is hovered */
.card-container:has(.card:hover) .card:not(:hover) {
opacity: 0.5;
transform: scale(0.98);
transition: all 200ms ease;
}
Link to section: 8. Responsive Component States8. Responsive Component States
/* Show a "read more" link only when the article has overflowing content */
.article:has(.article-content:has(> p:nth-child(4))) .read-more {
display: inline-block;
}
Link to section: Performance ConsiderationsPerformance Considerations
The browser engine evaluates :has() efficiently, but avoid deeply nested or overly complex selectors that scan large subtrees. Keep selectors scoped to reasonable DOM depths for optimal performance.
Embrace a New Era of Relational Styling:
The :has() selector marks a paradigm shift in CSS, unlocking patterns that previously required JavaScript or rigid HTML structures. With its ability to style parents based on descendants, this selector is set to change how we write component styles.
MDN Web Docs: :has() pseudo-class
Start exploring the power of :has() and unlock a new level of CSS expressiveness!