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

CSS :has() Selector

Frontend Intermediate
debt(d5/e3/b3/t5)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches' (d5). Broad :has() selectors causing performance issues require DevTools performance profiler to detect. Browser compatibility issues (Firefox <121) can be caught by caniuse-based linters or build tools, but performance problems from overly general selectors only surface during performance profiling or runtime testing.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix shows replacing JS class toggles with :has() is localized refactoring. Fixing performance issues means making selectors more specific (.card:has(.badge) instead of div:has(span)), which is a pattern replacement within CSS files but may touch multiple selectors across stylesheets.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3). :has() is a CSS selector feature that affects styling code only. Poor usage (broad selectors) creates a localized performance tax in rendering, but doesn't spread architectural weight across the system. The choice impacts CSS maintainers but doesn't shape application architecture or require other components to adapt.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap' (t5). The misconception that ':has() is slow in all cases' is a documented gotcha that developers eventually learn. The reality is nuanced—it's performant with specific class selectors but expensive with broad element selectors. This contradicts the simplistic mental model that all CSS selectors have similar performance characteristics, creating a notable but learnable trap.

About DEBT scoring →

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 66
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 3 pings M 1 ping T 0 pings W 0 pings T 2 pings F 0 pings S 0 pings S 1 ping M 0 pings T 2 pings W 1 ping T 0 pings F 1 ping S 1 ping S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Amazonbot 17 Perplexity 11 Google 5 ChatGPT 3 Ahrefs 3 Scrapy 3 Meta AI 2 Claude 2 SEMrush 2 Majestic 1 Qwen 1 Sogou 1 PetalBot 1
crawler 49 crawler_json 3
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