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