CSS can style an element according to what it contains with the :has() relational pseudo-class. For example, .card:has(img) selects a card that contains an image, and .card:has(> img) restricts the match to a direct child.

It also works with relationships other than descendants: h2:has(+ p) matches an h2 followed immediately by a paragraph.

A working checkbox example

Style a parent when its control is checkedLink

HTML and CSS

HTML

<label class="choice"><input type="checkbox" /> Include captions</label>

CSS

.choice {
  display: block;
  padding: 1rem;
  border: 2px solid #64748b;
}
.choice:has(input:checked) {
  border-color: #164eae;
  background: #dbeafe;
}

Edit HTML or CSS to update the preview as you type. Copy code snippet copies the selected tab. Preview spacing and font defaults are not included.

Check the box with a mouse or focus it with Tab and press Space. The label changes because its descendant matches input:checked. The control’s native checked state remains available to assistive technology, even when the CSS enhancement is unsupported.

Select exactly the relationship you mean

  • .card:has(img) looks for an image at any descendant depth.
  • .card:has(> img) looks only for a direct child image.
  • .field:has(input:invalid) can style a field containing an invalid input. Take care not to show an error before the person has had a chance to answer.
  • .card:has(img, video) matches either condition. .card:has(img):has(h2) requires both.

Specificity and limitations

The specificity comes from the most specific selector in the argument list. An ID in one argument can increase the rule’s specificity even when a different argument matched. The cascade lesson explains how that affects competing declarations.

You cannot nest :has() inside :has(). Pseudo-elements are generally not allowed inside its arguments unless a specification explicitly permits them. Broad selectors such as body:has(...) can require more work when a frequently changing descendant changes state; scope the selector to the component that needs it.

Browser support and a fallback

:has() is available in Chrome and Edge 105+, Safari 15.4+, and Firefox 121+. This describes the selector’s basic support, not every selector or feature you might put inside it.

.choice { border: 2px solid #64748b; }
@supports selector(:has(*)) {
  .choice:has(input:checked) { border-color: #164eae; }
}

Keep the base design usable. Essential behavior should not depend on a color change. For older browsers, an explicit class set by JavaScript can provide a similar style when that extra behavior is necessary.

Continue with child combinators to control descendant depth, or use focus-within when the only question is whether a component contains focus.

Source

MDN: :has() covers specificity, argument restrictions, examples, and browser compatibility.