← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

CSS :has() Selector

frontend Intermediate

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;
}

Added 23 Mar 2026
Views 35
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings W 0 pings T 0 pings F 3 pings S 0 pings S 1 ping M 0 pings T 0 pings W 0 pings T 0 pings F 2 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 3 pings S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 2 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T
No pings yet today
No pings yesterday
Amazonbot 14 Perplexity 11 Google 2 ChatGPT 1 Meta AI 1 Ahrefs 1
crawler 30
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) { }.

✓ schema.org compliant