Before :has(), CSS had no way to say "restyle the parent because it contains this kind of child" — highlighting a row whose checkbox is checked, or giving image-less cards a different layout, meant toggling a class from JS. :has() moves that into a pure CSS selector, like `.row:has(input:checked)` or `.card:not(:has(img))`.
Almost any selector can go inside the parentheses, so it also expresses sibling relationships (`.a:has(+ .b)`) and is popular for form validation styling (`.field:has(:invalid)`). With many conditions, the browser has to re-scan subtrees on every render, so it's worth watching performance on large lists.
Check caniuse/MDN baseline for current support. In browsers without it, the only option is toggling a class on the parent from JS when child state changes — and that remains a solid fallback today.
When to use
Form validation styling, layout branches based on "does this contain X", or propagating state up to a parent without JS.