Introduction
The :has() selector lets you select an element based on what it contains or what follows it — something CSS couldn't do before. It's often called the 'parent selector,' but it's more versatile than that.
Style a form group when its input is invalid, change a card layout when it contains an image, or highlight a list item when its checkbox is checked — no JavaScript needed.
It's one of the most powerful additions to CSS in recent years, enabling patterns that previously required JavaScript class toggling.
Key Concepts
Basic Syntax
.card:has(img) { grid-template-rows: 200px 1fr; }
h2:has(+ p) { margin-bottom: 0.5rem; }
.card:not(:has(img)) { padding: 2rem; }
Combining with Other Selectors
.field:has(input:invalid) { border-color: red; }
.field:has(input:focus) { box-shadow: 0 0 0 3px rgba(59,130,246,0.3); }
body:has(.modal.open) { overflow: hidden; }
Practical Examples
1. Conditional Card Layouts
.card:has(.card__image) {
display: grid;
grid-template-columns: 1fr 2fr;
}
.card:not(:has(.card__image)) {
display: block;
padding: 1.5rem;
}
2. Form Field States
.form-group:has(:required) label::after {
content: " *";
color: red;
}
3. Checkbox-Driven UI
.todo-item:has(input:checked) .todo-text {
text-decoration: line-through;
opacity: 0.6;
}
4. Interactive Navigation
nav:has(.dropdown:hover) {
background: rgba(0,0,0,0.95);
}
.nav-item:has(.dropdown:hover) > a {
color: var(--color-primary);
}
Best Practices
- Use :has() to replace JavaScript class toggling for visual state changes.
- Combine with :not() for powerful conditional styling.
- Keep selectors readable — deeply nested :has() hurts maintainability.
- Use for progressive enhancement — default styles serve as fallback.
- Test performance with many elements.
Common Pitfalls
- Performance in large DOMs — complex :has() selectors can be slower.
- :has() is forgiving — invalid selectors inside won't break but never match.
- Over-using :has() when a simple class or attribute selector is clearer.
- Not providing fallback styles for the ~9% without support.
Browser Support
Chrome 105+, Safari 15.4+, Firefox 121+, Edge 105+. ~91% global support.
Related Guides
- CSS Nesting — combine nesting with :has() for clean conditional styles
- CSS Pseudo-Elements Masterclass — conditionally show pseudo-elements
- Event Handling Best Practices — when CSS :has() replaces JS handlers