For decades, CSS developers have wished for a "parent selector" — a way to style an element based on what it contains. Traditional CSS selectors only work downward: you can select children based on their parents, but never the other way around.
The :has() pseudo-class changes everything. It's often called "the CSS feature we've been waiting 20 years for."
What is :has()?
The :has() pseudo-class selects an element if it contains at least one element matching the specified selector:
/* Select any <a> that contains an <img> */
a:has(img) {
display: block;
border: none;
}
/* Select any <form> that contains an invalid input */
form:has(input:invalid) {
border: 2px solid red;
}
flowchart TD
subgraph Traditional["Traditional CSS: Parent → Child"]
A["div.parent"] --> B[".child"]
end
subgraph HasSelector[":has() CSS: Child → Parent"]
C["div:has(.child)"] -.-> D[".child"]
D --> C
end
style Traditional fill:#f59e0b,color:#fff
style HasSelector fill:#10b981,color:#fff
Basic Syntax
The :has() selector takes a relative selector list as its argument:
/* Element contains another element */
parent:has(child) { }
/* Element contains element with specific class */
div:has(.highlight) { }
/* Element contains element in specific state */
form:has(input:focus) { }
/* Multiple conditions (OR logic) */
article:has(img, video) { }
Real-World Use Cases
1. Styling Forms Based on Validation State
One of the most practical uses is form validation feedback:
/* Highlight the entire form when any field is invalid */
form:has(:invalid) {
background-color: #fff5f5;
border-left: 4px solid #e53e3e;
}
/* Style form when all required fields are valid */
form:has(:required:valid):not(:has(:required:invalid)) {
background-color: #f0fff4;
border-left: 4px solid #38a169;
}
/* Enable submit button only when form is valid */
form:has(:invalid) button[type="submit"] {
opacity: 0.5;
pointer-events: none;
}
2. Card Layouts with Optional Elements
Style cards differently based on their content:
/* Cards with images get different layout */
.card:has(img) {
grid-template-rows: 200px 1fr;
}
.card:not(:has(img)) {
grid-template-rows: 1fr;
padding-top: 2rem;
}
/* Featured cards (with .featured class inside) */
.card:has(.featured) {
border: 2px solid gold;
box-shadow: 0 4px 12px rgba(255, 215, 0, 0.3);
}
3. Navigation with Active States
Highlight parent navigation items when a child link is active:
/* Style nav item when it contains the current page link */
nav li:has(a[aria-current="page"]) {
background-color: #edf2f7;
border-radius: 4px;
}
/* Style dropdown parent when a child is selected */
.dropdown:has(.selected) > .dropdown-toggle {
font-weight: bold;
color: #2b6cb0;
}
4. Quantity Queries
Apply styles based on the number of children:
/* Grid with many items uses different layout */
.gallery:has(> :nth-child(6)) {
grid-template-columns: repeat(3, 1fr);
}
/* Grid with few items */
.gallery:not(:has(> :nth-child(6))) {
grid-template-columns: repeat(2, 1fr);
}
/* List with only one item */
ul:has(> li:only-child) {
list-style: none;
padding-left: 0;
}
5. Sibling Selection Enhancement
:has() can also work with sibling combinators:
/* Style a heading that is followed by a subheading */
h1:has(+ .subheading) {
margin-bottom: 0.25rem;
}
/* Style label when its associated input is focused */
label:has(+ input:focus) {
color: #3182ce;
font-weight: bold;
}
/* Style input's container when input has content */
.input-group:has(input:not(:placeholder-shown)) label {
transform: translateY(-100%);
font-size: 0.75rem;
}
Combining :has() with Other Selectors
With :not()
/* Paragraphs not followed by another paragraph */
p:not(:has(+ p)) {
margin-bottom: 2rem;
}
/* Sections without any headings */
section:not(:has(h1, h2, h3)) {
padding-top: 1rem;
}
With :is() and :where()
/* Articles containing any media element */
article:has(:is(img, video, iframe, canvas)) {
container-type: inline-size;
}
/* Headers with any interactive child */
header:has(:where(button, a, input)) {
position: sticky;
top: 0;
}
Performance Considerations
:has() can be computationally expensive because it requires the browser to look at descendants to determine if an ancestor matches. Here are some tips:
/* AVOID: Very broad selectors */
:has(.some-class) { } /* Checks entire document */
/* BETTER: Scope to specific elements */
.container:has(.some-class) { }
/* AVOID: Deep descendant checks */
main:has(div div div .deep-element) { }
/* BETTER: Direct child or shallow checks */
main:has(> section > .element) { }
Browser Support
:has() has excellent modern browser support:
- Chrome 105+
- Firefox 121+
- Safari 15.4+
- Edge 105+
For older browsers, use feature detection:
/* Fallback styles */
.card {
border: 1px solid #ccc;
}
/* Enhanced styles for browsers with :has() support */
@supports selector(:has(*)) {
.card:has(.featured) {
border: 2px solid gold;
}
}
Common Patterns
Figure with Caption Styling
/* Figures with captions get margin adjustment */
figure:has(figcaption) {
margin-bottom: 2rem;
}
figure:has(figcaption) img {
margin-bottom: 0.5rem;
}
/* Figures without captions */
figure:not(:has(figcaption)) {
margin-bottom: 1rem;
}
Form Field Focus States
/* Float label pattern */
.field {
position: relative;
}
.field label {
position: absolute;
top: 50%;
transform: translateY(-50%);
transition: all 0.2s;
}
.field:has(input:focus) label,
.field:has(input:not(:placeholder-shown)) label {
top: 0;
font-size: 0.75rem;
color: #3182ce;
}
Dark Mode Based on Content
/* Auto dark mode for code blocks */
.content:has(pre code) {
--bg-color: #1a202c;
--text-color: #e2e8f0;
}
Summary
:has()selects elements based on their descendants (the "parent selector")- Use it for form validation, conditional layouts, and dynamic styling
- Combine with
:not(),:is(), and:where()for powerful selections - Be mindful of performance with very broad selectors
- Well-supported in modern browsers (2024+)
The :has() selector fundamentally changes what's possible with CSS. Patterns that previously required JavaScript—like styling a form based on its validity or a card based on its content—are now pure CSS. It's a powerful tool that belongs in every CSS developer's toolkit.
References
- MDN: :has() pseudo-class
- Grant, Keith. CSS in Depth, 2nd Edition. Manning Publications, 2024.
- Attardi, Joe. Modern CSS. Apress, 2025.