The `:has()` selector, sometimes called the "parent selector," reached broad browser support this year after being talked about for nearly as long as CSS Grid was before it shipped. The wait was frustrating at the time, but broad support arriving all at once meant we could start using it in production work immediately rather than gating it behind a feature detection fallback for months.
Styling a form group differently when it contains an invalid input, or a card differently when it contains an image, used to mean a small JavaScript helper toggling a class based on a child element's state. Both are now pure CSS: `.form-group:has(:invalid)` and `.card:has(img)` respectively, and we've already deleted a handful of small scripts that existed only to toggle a class based on a child element's state changing.
A less obvious but genuinely useful pattern showed up in a client's article layout: styling a heading differently depending on what follows it, `h2:has(+ p)` versus a heading immediately followed by another heading, without any JavaScript or extra markup to distinguish the two cases. We'd previously handled this with a content-management-side flag that editors had to remember to set correctly, which was exactly the kind of manual step that drifts out of correctness over time; the CSS-only version can't drift because there's no separate flag to forget.
Another practical win came in form validation styling more broadly. `:has()` let us style a fieldset's legend differently when any input inside it is currently invalid, giving a clearer at-a-glance signal on long forms about which section needs attention, again without a line of JavaScript tracking form state just to toggle a class.
The main caveat worth noting is performance on very large, deeply nested documents; `:has()` requires the browser to evaluate a subtree relationship rather than a simple selector match, and while we haven't hit a real-world case where this mattered, it's worth testing on markup-heavy pages rather than assuming it's free. For the ordinary use cases we've applied it to so far, form states, conditional card and layout styling, content-aware heading treatment, it's been a clean, welcome replacement for JavaScript that existed for no reason other than CSS's prior inability to look at a parent based on its children.
A navigation use case turned up unexpectedly useful as well: highlighting a parent navigation item when any of its dropdown children match the current page, `nav li:has(> a[aria-current="page"])`, which previously required a small script comparing the current URL against each nested link on page load. Removing that script also removed a source of flash-of-incorrect-state on slower page loads, since the CSS version applies before any JavaScript executes at all, a small but real accessibility and perceived-performance improvement that came along for free with the styling fix.
We did run into one genuine limitation worth flagging: `:has()` can't currently be used to style based on sibling elements that come before it in a way some developers initially expected, the selector looks forward and downward into descendants, or forward across siblings, not backward. A couple of early attempts to use it for a "style the label based on the checkbox state that comes after it in markup order" pattern worked fine, since that's a forward-sibling relationship, but a symmetrical backward case didn't, which is worth testing explicitly rather than assuming the selector is fully symmetrical in what it can see.