CSS :has() Selector

Beginner
⏱️ 7 min read
📚 Updated: Jul 2026
🎯 4 Examples
Relational

What You’ll Learn

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.

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.

📝 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;
}
.box:has(a) form:has(:invalid) li:has(input:checked) .card:has(img)

Syntax Rules

  • The selector inside :has() can be an element, class, ID, attribute, or pseudo-class.
  • Use :not(:has(...)) to match elements that do not contain a match.
  • Styles apply to the element with :has(), not the inner matched element.
  • Specificity includes both the outer selector and the inner selector.
  • Keep selectors simple for readability and performance on large pages.

Related Selectors

  • :is() — matches if any selector in a list matches the element itself
  • :where() — like :is() but with zero specificity
  • :not() — excludes matches; combine with :has() for inverse logic
  • element > element — child combinator (descendant-only, no parent styling)
  • :checked, :invalid — common inner selectors for forms and lists

⚡ Quick Reference

QuestionAnswer
Selector typeRelational pseudo-class (functional)
What it matchesAn element that contains (or relates to) a matching descendant
Common nicknameParent selector
Inverse pattern:not(:has(selector))
Primary useConditional layout and state styling without JavaScript
Browser supportChrome 105+, Firefox 121+, Safari 15.4+, Edge 105+

When to Use :has()

:has() shines when the style of a container depends on its contents:

  • Parent highlighting — Style a card, row, or section when it contains a link, image, or badge.
  • Dynamic layouts — Change grid or flex layout based on what children are present.
  • Form validation — Highlight a form when any field is invalid: form:has(:invalid).
  • Interactive lists — Style a list item when its checkbox is checked: li:has(input:checked).
  • Conditional UI — Show visual cues on containers without adding extra classes in HTML or JavaScript.

👀 Live Preview

The first box contains a link and gets the blue highlight from .box:has(a). The second box has no link and keeps the default gray style:

This is a box with text only.

Examples Gallery

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>

<div class="box">
  <p>This box has text only.</p>
</div>
<div class="box">
  <p>Contains an <a href="#">anchor</a>.</p>
</div>
Try It Yourself

How It Works

Only the second .box matches :has(a) because it contains an <a> element. The styles apply to the parent div, not the link itself.

Example 2 — Highlight a form with invalid fields

Use form:has(:invalid) to add a warning border when any required field fails validation.

has-form.css
form:has(:invalid) {
  border: 2px solid #dc2626;
  background: #fef2f2;
  padding: 1rem;
  border-radius: 0.5rem;
}

input:invalid {
  border-color: #dc2626;
}
Try It Yourself

How It Works

When any descendant input is invalid, the entire form matches :has(:invalid) and receives the red warning style — no JavaScript required.

📄 Lists & Layouts

Apply conditional styles to list items and cards based on their contents.

Example 3 — Style a checked todo item

Strike through list items that contain a checked checkbox.

has-checked.css
li:has(input:checked) {
  text-decoration: line-through;
  color: #94a3b8;
}

li {
  list-style: none;
  padding: 0.35rem 0;
}
Try It Yourself

How It Works

li:has(input:checked) matches the list item (parent), so you can style the entire row when its checkbox is checked.

Example 4 — Card layout when an image is present

Give cards with images a stronger border and shadow using .card:has(img).

has-card.css
.card:has(img) {
  border: 2px solid #2563eb;
  box-shadow: 0 4px 12px rgba(37, 99, 235, 0.15);
}

.card {
  border: 1px solid #e2e8f0;
  border-radius: 0.65rem;
  padding: 1rem;
  max-width: 14rem;
}
Try It Yourself

How It Works

The card with an image inside matches :has(img) and gets enhanced styling. Text-only cards keep the default subtle border.

💬 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.

⚠️ 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.

♿ 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.

🖥 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 Chrome 105+ · Desktop & Mobile
Full support
Mozilla Firefox 121+ · Desktop & Mobile
Full support
Apple Safari 15.4+ · macOS & iOS
Full support
Microsoft Edge 105+
Full support
Opera 91+ (Chromium-based)
Full support
:has() relational pseudo-class 92% supported

Bottom line: Safe for modern projects. Avoid relying on it alone for critical UX in environments that must support very old browsers.

🎉 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.

💡 Best Practices

✅ Do

  • Use :has() for conditional parent styling
  • Combine with form pseudo-classes like :invalid
  • Keep inner selectors short and readable
  • Test cascade specificity with other rules
  • 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

Key Takeaways

Knowledge Unlocked

Five things to remember about :has()

Use these points when styling parents based on children.

5
Core concepts
() 02

:has(sel)

Inner selector.

Syntax
📝 03

Forms & lists

Real use cases.

Uses
:not 04

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.

Practice in the Live Editor

Open the HTML editor and experiment with :has(), parent selectors, and conditional styling.

HTML Editor →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful