:has() Selector Tester

The :has() selector has been called the "parent selector" that CSS developers wished for over 20 years. It lets you style an element based on its descendants — for example, style any form that contains an invalid field, or any card that has an image. All major browsers support :has() since late 2023. This tester gives you a live HTML sandbox to experiment with :has() patterns before committing them to your codebase.

What is :has()?

The :has() pseudo-class is often described as the long-awaited "parent selector" for CSS. It selects an element if any of its descendants (or subsequent siblings, depending on the combinator) match the argument selector. Before :has(), there was no pure-CSS way to style a parent based on what it contains — developers had to use JavaScript for tasks like highlighting a form group when its input was invalid, or adding a border to a card only when it includes an image. With :has(), these patterns become a single line of CSS, making stylesheets more expressive and reducing JavaScript dependencies.

The syntax is straightforward: you write a regular selector, then append :has() with an argument that describes what the element must contain (or be followed by) for the rule to match. For example, article:has(img) selects every <article> element that contains at least one <img> descendant. The argument can be any valid selector, including pseudo-classes like :checked, :invalid, or :focus, which opens up powerful state-based styling possibilities that previously required JavaScript event listeners.

What makes :has() truly revolutionary is that it is not limited to parent-child relationships. Combined with sibling combinators, it can select an element based on its next or subsequent siblings. For instance, h2:has(+ p) selects any <h2> that is immediately followed by a <p>. This ability to look forward in the DOM without JavaScript fundamentally changes what is possible in pure CSS, enabling patterns like "style a label differently when the next input has focus" or "reduce heading margin when followed by a subtitle."

How to use this tool

Enter a CSS selector using :has() in the selector input field at the top — for example, article:has(img) or form:has(:invalid). Edit the HTML in the textarea below to set up the document structure you want to test against. The preview panel renders your HTML live and highlights every element that matches your selector with an accent-colored outline. The match counter below the HTML input shows how many elements match, making it easy to verify your selector logic.

Both code output panels update automatically with copy-ready CSS you can paste into your project. The "Direct CSS" output gives you a plain rule with the selector and a highlight style. The "CSS Variables" version wraps the highlight values in custom properties for easier customization. Use the share URL button to save your exact selector and HTML combination and share it with teammates or bookmark it for later reference.

Practical examples

Form validation styling

Highlight form groups that contain invalid inputs, giving users immediate visual feedback about which fields need attention. The :has(:invalid) pattern lets the parent container react to the validation state of any input inside it.

/* Highlight the entire form group when its input is invalid */
.form-group:has(:invalid) {
  border-left: 3px solid #ef4444;
  background: #fef2f2;
  padding-left: 1rem;
}

/* Style the label within an invalid group */
.form-group:has(:invalid) label {
  color: #dc2626;
}

/* Add a success style for valid groups */
.form-group:has(:valid) {
  border-left: 3px solid #22c55e;
}

Card with optional image

Cards that may or may not contain an image can use :has() to apply different layouts depending on whether an image is present. This eliminates the need for conditional CSS classes set by JavaScript or a template engine.

/* Default card layout: single column */
.card {
  display: grid;
  gap: 1rem;
  padding: 1.5rem;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* When the card contains an image, use a two-column layout */
.card:has(img) {
  grid-template-columns: 200px 1fr;
}

/* Full-width image treatment */
.card:has(img) img {
  border-radius: 6px;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

Navigation with active state propagation

Style a navigation list item (or its parent menu) based on the presence of an active or current-page link. This pattern propagates the "active" state from a child link up to its parent container using pure CSS.

/* Highlight the nav item containing the current page link */
nav li:has(> a[aria-current="page"]) {
  background: var(--accent);
  border-radius: 6px;
}

nav li:has(> a[aria-current="page"]) a {
  color: #fff;
  font-weight: 600;
}

/* Expand a dropdown if it contains the current page */
nav .dropdown:has(a[aria-current="page"]) .dropdown-menu {
  display: block;
}

Quantity-aware lists

Style a list differently based on how many items it contains. By combining :has() with :nth-child(), you can detect how many children an element has and apply different layouts accordingly.

/* Single item: center it */
.item-list:has(> :only-child) {
  display: flex;
  justify-content: center;
}

/* More than 3 items: switch to grid */
.item-list:has(> :nth-child(4)) {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1rem;
}

Common patterns and best practices

The :has() selector is incredibly versatile, but following a few guidelines will keep your CSS readable and performant.

Browser support

The :has() pseudo-class is supported in all major browsers since December 2023. Safari was the first to ship it in version 15.4 (March 2022). Chrome and Edge followed in version 105 (August 2022). Firefox completed the rollout in version 121 (December 2023). It is safe to use in production for any project targeting modern browsers. Because an unsupported pseudo-class invalidates the entire selector rule in older browsers, you may want to isolate :has() rules from critical base styles if you need to support very old browser versions.

FAQ

Why was :has() so hard to implement?

Browser engines traditionally process CSS selectors right-to-left for performance — they start at a candidate element and walk up the DOM tree to check if ancestors match. The :has() pseudo-class reverses this flow because the browser must look downward at an element's descendants to decide whether the element itself matches. Implementing this efficiently without degrading page rendering performance required significant engine work, which is why :has() took years to land in browsers despite being in CSS specifications much earlier.

Can I nest :has()?

Yes, you can nest :has() selectors. For example, .a:has(.b:has(.c)) selects any .a element that contains a .b which itself contains a .c. This is valid CSS and works in all browsers that support :has(). However, deeply nested :has() selectors can be hard to read and may have performance implications on very large DOMs, so use nesting sparingly and consider whether a simpler selector achieves the same result.

Can I use :has() with combinators like + and ~?

Yes, :has() supports all CSS combinators. For example, h2:has(+ p) selects an <h2> immediately followed by a <p> element, enabling next-sibling styling. Similarly, h2:has(~ p) matches an <h2> that has any subsequent sibling <p>. This makes :has() far more than just a parent selector — it is a general-purpose relational selector that can look at descendants, children, and siblings.

Does :has() affect CSS specificity?

The :has() pseudo-class itself adds no specificity to the selector. However, the most specific selector inside the :has() argument does count toward the overall specificity of the compound selector. For example, div:has(#main) has the specificity of a type selector (div) plus an ID selector (#main), resulting in (1, 0, 1). This follows the same specificity rules as :is() and :not().

Is :has() slow?

Browsers have optimized :has() to be fast for typical use cases. Common patterns like .parent:has(.child) or form:has(:invalid) run efficiently in all modern engines. To keep performance optimal, avoid deeply nested :has() selectors, avoid combining :has() with the universal selector on very large DOMs (like *:has(img)), and prefer direct child combinators (:has(> .child)) over descendant searches when possible.

Can I use :has() in JavaScript's querySelector?

Yes, document.querySelector('article:has(img)') works in all browsers that support :has() in CSS. It follows the same selector syntax and matching logic. This means you can use :has() in querySelector, querySelectorAll, matches, and closest. It is a powerful way to find elements based on their contents without writing manual DOM traversal code.

Related tools