The Undeniable Utility Of CSS :has The article discusses the CSS `:has` pseudo-class, a modern feature that allows styling a parent element based on its children, reversing traditional top-down CSS selectors. It highlights real-world use cases, such as improving focus outlines on interactive cards, and notes that while browser support is at ~92%, fallback styles can be provided using the `@supports` rule. The author, initially skeptical, found `:has` to be an incredibly handy utility even in CSS-in-JS contexts like React. Introduction I don’t know if you’ve noticed, but the CSS world has been on fire recently. 🔥 Behind the scenes, all major browser vendors and the CSS specification authors have been working together to deliver tons of highly-requested CSS features. Things like container queries, native CSS nesting, relative color syntax, balanced text, and so much more. One of these new features is the :has pseudo-class. And, honestly, I wasn’t sure how useful it would be for me. I mostly build webapps using React, which means I tend not to use complex selectors. Would the :has pseudo-class really offer much benefit in this context? Well, I’ve spent the past few months rebuilding this blog, using all of the modern CSS bells and whistles. And my goodness, I was wrong about :has. It’s an incredibly handy utility, even in a CSS-in-JS context In this blog post, I'll introduce you to :has and share some of the most interesting real-world use cases I’ve found so far, along with some truly mindblowing experiments. Link to this heading the-basics-1 The basics Historically, CSS selectors have worked in a “top down” fashion. For example, by separating multiple selectors with a space, we can selectively style a child based on its parent : Code Playground Result The :has pseudo-selector works in a “bottom up” fashion; it allows us to style a parent based on its children: Code Playground Result This might not seem like a big deal, but it opens so many interesting new doors. Over the past few months, I’ve had one epiphany after another, moments where I went “Woah, that means I can do this??” Link to this heading browser-support-2 Browser support Before we get to all the cool demos, we should briefly talk about browser support. :has is supported in all 4 major browsers, starting from: - Safari 15.4, introduced in March 2022 - Chrome/Edge 105, introduced in August 2022 - Firefox 121, introduced in December 2023 As I write this in September 2024, :has is at ~92% browser support. Here's a live embed with up-to-date values: Honestly, 92% isn’t great when it comes to browser support… That means roughly 1 in 12 people are using an unsupported browser Fortunately, most of the use cases I’ve found for :has are optional “nice-to-have” bonuses, so it’s not really a big deal if they don’t show up for everyone. And in other cases, we can use feature detection to provide fallback CSS. Link to this heading feature-detection-3 Feature detection The @supports at-rule allows us to apply CSS conditionally, based on whether or not it’s supported by the user’s browser. Here’s what it looks like: p { / Fallback styles here / } @supports selector p:has a { p:has a { / Fancy modern styles here / } } If the selector passed to the selector function isn’t understood by the current browser, everything within is ignored. And if the user’s browser is even older, and doesn’t recognize the @supports at-rule, then the whole block is ignored. Either way, it works out. Now, the thing is, there is no way to “mimic” :has using older CSS. Our fallback styles won’t really be able to reproduce the same effect. Instead, we should think of it as having two sets of styles that accomplish the same goal in different ways. I'll include an example in the next section. Link to this heading styling-based-on-states-4 Styling based on states On this blog’s new “About Josh” page /about-josh/ , I use a “bento box” layout containing a bunch of little cards. Some of these cards have clickable children: For folks who navigate with a keyboard, however, the experience was a bit more funky. Some of the children dynamically change size, leading to curious focus outlines like this: To solve this problem, I moved the focus outline to the parent container. Here’s what it looks like now: This solves our problem, and I think it also looks pretty nice Let’s dig into how this works. Here’s roughly what the HTML looks like: