CSS :has() Selector
Also Known As
CSS :has
parent selector CSS
has selector
CSS relational pseudo-class
TL;DR
CSS :has() is a parent selector — .card:has(.badge) matches a .card that contains a .badge — enabling styles that depend on children or siblings, which previously required JavaScript.
Explanation
:has() accepts a relative selector list and matches elements for which the relative selector matches. It can look forward (parent contains child), sideways (h1:has(+ p) — h1 followed by p), and combine with other pseudo-classes. :has() has been called the 'holy grail' of CSS selectors because it closes the gap between CSS and JavaScript-based conditional styling. Supported in Chrome 105+, Safari 15.4+, Firefox 121+. Performance: browsers evaluate :has() bottom-up, which can be expensive for broad selectors; specificity is calculated including the argument's specificity.
Common Misconception
✗ :has() is slow in all cases. :has() can be expensive when used broadly (*, div:has(span) on thousands of elements), but is performant for specific class selectors (.card:has(.badge)). Use specific selectors and test with DevTools performance profiler.
Why It Matters
Before :has(), making a parent element look different based on its children required adding a class via JavaScript. :has() eliminates entire categories of 'add a class when this child exists' JavaScript. It directly enables patterns like 'highlight a form row when its input is invalid', 'show a tooltip parent when the button has focus', and 'style a card differently when it contains an image'.
Common Mistakes
- Using broad :has() selectors — div:has(span) matches every div containing any span; be specific with classes to avoid performance issues.
- Expecting :has() to work in Firefox before 121 — Firefox added :has() in December 2023; check your support requirements.
- Nesting :has() inside :has() — doubly nested relational pseudo-classes are not well supported; keep :has() at the top level.
- Replacing all JavaScript conditionals with :has() — :has() is CSS; it cannot trigger callbacks or update data. Use it for visual state only, not business logic.
Code Examples
✗ Vulnerable
/* ❌ JavaScript to add parent class when child state changes */
/* CSS */
.form-group.has-error { border-left: 3px solid red; }
.card.has-image { padding: 0; }
/* JavaScript — required just for parent styling */
document.querySelectorAll('input').forEach(input => {
input.addEventListener('invalid', () => {
input.closest('.form-group').classList.add('has-error');
});
input.addEventListener('input', () => {
if (input.validity.valid)
input.closest('.form-group').classList.remove('has-error');
});
});
✓ Fixed
/* ✅ :has() — no JavaScript needed for parent styling */
/* Highlight the form group when its input is invalid */
.form-group:has(input:invalid) {
border-left: 3px solid red;
}
/* Remove card padding when it contains an image */
.card:has(> img) {
padding: 0;
}
/* Style a nav item when its dropdown is open */
.nav-item:has(.dropdown[open]) {
background: rgba(0,0,0,.05);
}
/* h2 followed immediately by p — adjacent sibling */
h2:has(+ p) {
margin-bottom: 4px; /* Less space when followed by text */
}
/* Dark mode toggle without JS */
body:has(#dark-mode:checked) {
background: #1a1a1a;
color: #e0e0e0;
}
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
23 Mar 2026
Views
35
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 0
No pings yet today
No pings yesterday
Amazonbot 14
Perplexity 11
Google 2
ChatGPT 1
Meta AI 1
Ahrefs 1
Also referenced
How they use it
crawler 30
Related categories
⚡
DEV INTEL
Tools & Severity
⚙ Fix effort: Low
⚡ Quick Fix
Replace JavaScript that adds a class to a parent when a child matches a condition with :has(). Example: removing the JS class toggle for .form-group.has-error { } and instead using .form-group:has(input:invalid) { }.