I’ve been carrying a tiny pile of JavaScript in our forms layer for years: a MutationObserver that watched for :invalid children appearing anywhere inside a form, and toggled a class on the parent so the wrapping card could go red. It worked. It also broke every time someone added a new field type, or wrapped an input in a portal, or rendered into a shadow root.

Now :has() ships in every evergreen browser, and the whole apparatus collapses to one selector: form:has(:invalid) { … }. No observers, no class names, no race between paint and the next tick. The browser does the work; the CSS reads like the rule you wished you could write the first time.

I spent the morning deleting things, which is the best kind of morning. The diff was −94/+3, and three components dropped a dependency on a hook that nobody fully remembered. The biggest win wasn’t lines of code; it was that the styling logic now lives entirely in the stylesheet, where designers can change it without filing a ticket.

Caveats: :has() is still a cost-per-paint, not free. Don’t put it on the document root unless you mean it. I scoped each rule to the smallest sensible ancestor (the form, not the page), and the inspector showed no measurable hit on a complex onboarding flow.

Lesson reinforced: when a platform feature shows up, the right move is usually to delete code, not refactor it. A new tool doesn’t earn its keep until something old has been retired.