:has() lets CSS style an element based on what it contains. That makes parent-aware styling possible without adding a class in many cases.
The temptation is to use it everywhere. The safer rule is this: use :has() when CSS is responding to visible document state, not when it is trying to own application logic.
Good use: form state
.field:has(input:invalid) {
--field-border: #d85d3f;
}
.field:has(input:focus-visible) {
--field-border: #0f8b8d;
}
The field is styling itself based on the actual state of its input. There is no hidden data model. The selector mirrors the document.
Good use: content-aware layout
.media-card:has(img) {
grid-template-columns: 10rem minmax(0, 1fr);
}
This can be reasonable when the card’s layout really depends on whether media exists. The markup already contains that fact.
Risky use: product rules
.plan:has([data-tier="enterprise"]) {
order: -1;
}
This may work, but it hides a product decision in CSS. If enterprise plans should sort first, the data or template should own that order. CSS can style the result.
Performance and readability
Modern engines support :has(), but selector clarity still matters. A rule like .app:has(main article .card form input:checked) is hard to debug because it ties distant parts of the document together. Keep :has() close to the component boundary.
A useful team guideline
If the selector can be explained as “style this component when it contains this visible state,” it is probably a good fit. If it sounds like “change product behavior when a distant data condition exists,” move the decision out of CSS.