The :has() Selector
The Problem
CSS has never had a way to select a parent element based on its children. Developers have relied on JavaScript to toggle classes for parent-child state relationships, such as highlighting a form group when its input is invalid, or changing a card layout based on whether it contains an image. AI agents almost never use :has() and instead suggest JavaScript-based solutions for these patterns.
The Solution
The :has() relational pseudo-class selects elements that contain at least one element matching the given selector list. It acts as a "parent selector" but is far more powerful: it can look at any relative position (children, siblings, descendants) to conditionally apply styles.
Code Examples
Basic Parent Selection
/* Style a card differently when it contains an image */
.card:has(img) {
grid-template-rows: 200px 1fr;
}
.card:has(img) .card-body {
padding-top: 0;
}
Form Validation Styling
Style form groups based on input validity without JavaScript.
/* Highlight the entire field group when input is invalid */
.field-group:has(:user-invalid) {
border-left: 3px solid red;
background: #fff5f5;
}
.field-group:has(:user-invalid) .error-message {
display: block;
}
/* Style label when its sibling input is focused */
.field-group:has(input:focus) label {
color: blue;
font-weight: bold;
}
<div class="field-group">
<label for="email">Email</label>
<input type="email" id="email" required />
<span class="error-message">Please enter a valid email</span>
</div>
Quantity Queries
Adapt layout based on the number of children, with no JavaScript required.
/* Switch to grid layout when a list has 5 or more items */
.item-list:has(> :nth-child(5)) {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
/* Single-column layout for fewer items */
.item-list:not(:has(> :nth-child(5))) {
display: flex;
flex-direction: column;
}
/* Style based on even/odd number of children */
.grid:has(> :last-child:nth-child(even)) {
/* Even number of children */
grid-template-columns: repeat(2, 1fr);
}
.grid:has(> :last-child:nth-child(odd)) {
/* Odd number of children */
grid-template-columns: repeat(3, 1fr);
}
Styling Based on Sibling State
/* Change page layout when a sidebar checkbox is checked */
body:has(#sidebar-toggle:checked) .main-content {
margin-left: 0;
}
body:has(#sidebar-toggle:checked) .sidebar {
transform: translateX(-100%);
}
Combining with Other Selectors
/* Style a navigation item that contains the current page link */
nav li:has(> a[aria-current="page"]) {
background: #e0e7ff;
border-radius: 4px;
}
/* Style a table row that has an empty cell */
tr:has(td:empty) {
opacity: 0.6;
}
Using :has() with Direct Child Combinator
Use the direct child combinator > for better performance. It limits the browser's search to immediate children instead of all descendants.
/* Preferred: direct child (faster) */
.container:has(> .alert) {
border: 2px solid red;
}
/* Avoid when possible: descendant (slower on large DOMs) */
.container:has(.alert) {
border: 2px solid red;
}
Browser Support
- Chrome 105+
- Safari 15.4+
- Firefox 121+
- Edge 105+
Global support exceeds 96%. Feature detection is available with @supports selector(:has(*)).
Common AI Mistakes
- Suggesting JavaScript class toggling when
:has()solves the problem in pure CSS - Not knowing
:has()exists and recommending workarounds - Using descendant selectors inside
:has()when direct child>would be more performant - Not combining
:has()with:not()for inverse logic (e.g.,.card:not(:has(img))) - Forgetting that
:has()can look at siblings, not just descendants (e.g.,h2:has(+ p)) - Attempting to polyfill
:has()— it requires real-time DOM awareness and cannot be efficiently polyfilled
When to Use
- Parent styling based on child state (form validation, content-aware layouts)
- Quantity queries to adapt layout based on number of children
- State-driven styling without JavaScript (checkbox hacks, focus management)
- Conditional component styling based on content presence