{
    "slug": "css_has_selector",
    "term": "CSS :has() Selector",
    "category": "frontend",
    "difficulty": "intermediate",
    "short": "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.",
    "long": ":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.",
    "aliases": [
        "CSS :has",
        "parent selector CSS",
        "has selector",
        "CSS relational pseudo-class"
    ],
    "tags": [
        "css",
        "frontend",
        "selectors",
        "modern-css",
        "conditional-styling"
    ],
    "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."
    ],
    "when_to_use": [],
    "avoid_when": [],
    "related": [
        "css_nesting",
        "css_cascade",
        "css_specificity",
        "css_custom_properties"
    ],
    "prerequisites": [],
    "refs": [
        "https://developer.mozilla.org/en-US/docs/Web/CSS/:has",
        "https://caniuse.com/css-has"
    ],
    "bad_code": "/* ❌ JavaScript to add parent class when child state changes */\n/* CSS */\n.form-group.has-error { border-left: 3px solid red; }\n.card.has-image { padding: 0; }\n\n/* JavaScript — required just for parent styling */\ndocument.querySelectorAll('input').forEach(input => {\n    input.addEventListener('invalid', () => {\n        input.closest('.form-group').classList.add('has-error');\n    });\n    input.addEventListener('input', () => {\n        if (input.validity.valid)\n            input.closest('.form-group').classList.remove('has-error');\n    });\n});",
    "good_code": "/* ✅ :has() — no JavaScript needed for parent styling */\n/* Highlight the form group when its input is invalid */\n.form-group:has(input:invalid) {\n    border-left: 3px solid red;\n}\n\n/* Remove card padding when it contains an image */\n.card:has(> img) {\n    padding: 0;\n}\n\n/* Style a nav item when its dropdown is open */\n.nav-item:has(.dropdown[open]) {\n    background: rgba(0,0,0,.05);\n}\n\n/* h2 followed immediately by p — adjacent sibling */\nh2:has(+ p) {\n    margin-bottom: 4px; /* Less space when followed by text */\n}\n\n/* Dark mode toggle without JS */\nbody:has(#dark-mode:checked) {\n    background: #1a1a1a;\n    color: #e0e0e0;\n}",
    "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) { }.",
    "effort": "low",
    "created": "2026-03-23",
    "updated": "2026-03-23",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/css_has_selector",
        "html_url": "https://codeclaritylab.com/glossary/css_has_selector",
        "json_url": "https://codeclaritylab.com/glossary/css_has_selector.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[CSS :has() Selector](https://codeclaritylab.com/glossary/css_has_selector) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/css_has_selector"
            }
        }
    }
}