The :has() pseudo-class lets you style an element based on what it contains or what is inside it. It is CSS’s long-awaited “parent selector.”
01
Parent match
Style containers.
02
Syntax
:has(selector)
03
Descendants
Child-based rules.
04
Forms
Validation styling.
05
:not(:has())
Inverse matching.
06
Modern CSS
Less JavaScript.
Fundamentals
Introduction
The :has() selector in CSS is a relational pseudo-class that allows you to style elements based on their descendants or related elements inside them.
Before :has(), CSS could only travel downward in the DOM tree — for example, .card img styles an image inside a card. With :has(), you can flip that logic and style the card when it contains an image: .card:has(img).
Definition and Usage
Append :has() to a selector and pass a valid CSS selector inside the parentheses. The outer element matches only if at least one element inside it (or related to it) matches the inner selector.
💡
Beginner Tip
Think of :has() as a question: “Does this element have something matching this selector inside it?” If yes, your styles apply to the outer element — not the child.
Foundation
📝 Syntax
The syntax for the :has() pseudo-class is:
syntax.css
element:has(selector){/* CSS properties */}
Parameters
element — The element you want to style (the parent or container).
selector — A valid CSS selector matching one or more elements inside (or related to) the outer element.
Basic Example
has-box.css
/* Style a box only when it contains a link */.box:has(a){border:2px solid #2563eb;background-color:#eff6ff;}/* Default box style */.box{border:2px solid #cbd5e1;background-color:#f1f5f9;}
Practice :has() with parent highlighting, form validation, checked list items, and image cards.
📜 Core Patterns
Style parent elements when they contain a matching child.
Example 1 — Highlight a box that contains a link
Apply a blue border and background to .box elements that contain an anchor tag.
has-box.html
<style>.box:has(a){border:2px solid #2563eb;background-color:#eff6ff;}.box{border:2px solid #cbd5e1;background:#f1f5f9;padding:1rem;border-radius:0.5rem;}</style><divclass="box"><p>This box has text only.</p></div><divclass="box"><p>Contains an <ahref="#">anchor</a>.</p></div>
The card with an image inside matches :has(img) and gets enhanced styling. Text-only cards keep the default subtle border.
Tips
💬 Usage Tips
Targeting parents — Style a container when it holds a button, input, image, or any specific child element.
Dynamic layouts — Adjust spacing, borders, or grid columns based on content without JavaScript class toggling.
Form validation — Combine with :invalid, :valid, or :required for visual feedback on entire forms or field groups.
Inverse matching — Use :not(:has(img)) to style elements that lack a specific child.
Readability first — Long, nested :has() chains are hard to maintain; prefer simple, scoped selectors.
Watch Out
⚠️ Common Pitfalls
Expecting content changes — :has() is for styling only; it cannot add, remove, or move DOM nodes (use JavaScript for that).
Overly complex selectors — Deeply nested :has() on large pages can impact performance; profile if you notice slowdowns.
Styling the wrong element — Remember styles apply to the element with :has(), not the inner matched child.
Legacy browser gaps — Very old browsers lack support; provide fallbacks or progressive enhancement when needed.
Specificity surprises — The inner selector adds specificity; test cascade order when combining with other rules.
A11y
♿ Accessibility
Do not rely on color alone — When highlighting invalid forms, also use text labels or icons that screen readers can access in HTML.
Preserve focus indicators — Parent highlighting should not hide or remove child focus rings.
Checked state — Ensure checkboxes have associated <label> elements; :has() styling is supplementary.
Progressive enhancement — Pages should remain usable even if :has() styles are not applied.
Contrast — Warning borders and backgrounds must meet WCAG contrast requirements.
🧠 How :has() Works
1
Browser scans the DOM
For each candidate element, the browser checks descendants against the inner selector.
Scan
2
Match found inside
If at least one descendant matches the selector in :has(), the parent qualifies.
Match
3
Parent styles apply
CSS rules on the outer element take effect — borders, backgrounds, layout changes.
Style
=
🔗
Conditional CSS, no JS
Layout and state styling driven purely by HTML structure.
Compatibility
🖥 Browser Compatibility
:has() is supported in all current major browsers. It landed in Chrome 105, Safari 15.4, Firefox 121, and Edge 105.
✓ Baseline · Modern browsers
Parent selector is here
:has() works in current Chrome, Firefox, Safari, and Edge. Use progressive enhancement for legacy targets.
92%Global support
Google Chrome105+ · Desktop & Mobile
Full support
Mozilla Firefox121+ · Desktop & Mobile
Full support
Apple Safari15.4+ · macOS & iOS
Full support
Microsoft Edge105+
Full support
Opera91+ (Chromium-based)
Full support
:has() relational pseudo-class92% supported
Bottom line: Safe for modern projects. Avoid relying on it alone for critical UX in environments that must support very old browsers.
Wrap Up
🎉 Conclusion
The :has() selector is a powerful addition to the CSS toolkit. It lets you apply styles based on parent-child relationships without relying on JavaScript — something developers have wanted for years.
Use it for conditional layouts, form feedback, and content-aware styling. Keep selectors simple, test in your target browsers, and pair with progressive enhancement when legacy support matters.
Provide fallbacks for legacy browsers when required
❌ Don’t
Use it to manipulate DOM content
Chain many nested :has() calls
Forget styles apply to the parent, not the child
Rely on color alone for validation feedback
Assume universal support without checking targets
Summary
Key Takeaways
Knowledge Unlocked
Five things to remember about :has()
Use these points when styling parents based on children.
5
Core concepts
🔗01
Parent selector
Style containers.
Purpose
()02
:has(sel)
Inner selector.
Syntax
📝03
Forms & lists
Real use cases.
Uses
:not04
Inverse
:not(:has()).
Pattern
🌐05
92% support
Modern browsers.
Compat
❓ Frequently Asked Questions
The :has() pseudo-class matches an element if it contains (or is followed by, depending on the argument) at least one element that matches the selector inside the parentheses. It is often called the parent selector because you can style a container based on its contents.
Yes, in practice. Before :has(), CSS could only style descendants from an ancestor (e.g. .parent .child). :has() lets you style the ancestor when a descendant matches — for example, .card:has(img) styles the card when it contains an image.
You can pass most valid selectors: element types, classes, IDs, pseudo-classes like :checked or :invalid, and combinators. Complex selectors are allowed, but keep them readable for maintainability.
Use :not(:has(...)). For example, .box:not(:has(a)) matches boxes that do not contain a link.
:has() is supported in all current versions of Chrome, Firefox, Safari, and Edge (roughly 2022 onward). Always verify support if you must support very old browsers.