We can :has it all

Posted on December 19, 2023
Takes about 4 minutes to read

The functional :has() CSS pseudo class is now shipping in all evergreen browsers! 🎉

With the release of Firefox 121.0, I'm excited to see that my semi-dusty :has() demos are finally realizing their full potential in Firefox. The amount of opportunity unlocked with this selector seems nearly infinite. It can simplify some of the more complex CSS selectors and hacks used in the past. It also opens the door to replacing JavaScript solutions that weren't yet possible to achieve with only CSS.

This post is merely a celebration of :has() browser support and shares a quick dive into some of my previous experiments. At the end of this article are helpful resources that do an amazing job explaining how the selector works and the unbelieveable power it gives us.

Themes, layouts, and filters

This first demo showcases how :has() can be used to set a dark mode, change the layout, and toggle the visibility of elements. All of it can be achieved through a combination of the :has() and :checked selectors.

Open CodePen demo

To pull off this primitive filtering technique, each card has a data-category attribute. When a filter option is selected, only the cards with that particular category will remain visible. Check out the following HTML example:

<article class="card" data-category="bakery">
  <!-- card contents -->
</article>
<article class="card" data-category="taquería">
  <!-- card contents -->
</article>
<article class="card" data-category="café">
  <!-- card contents -->
</article>

If the bakery filter option is selected, then the second and third cards would be hidden. Here's the CSS that hides all non-bakery cards:

body:has([name="filter"][value="bakery"]:checked) .card:not([data-category="bakery"]) {
  display: none;
}

[name="filter"] can be omitted from the selector above given the current circumstances. As things get more complex, however, there could be value overlaps that cause unintended results. This explicitness does raise specificity, but it can be reduced by using a :where() selector if preferred. Semi-related: The truth about CSS selector performance is a good read!

Using :has() to alter layout and filter collections of elements is incredibly powerful. Although, let's understand that there are important accessibility considerations to make here. Don't do something like this in production without ensuring all folks are enabled with a proper experience.

Skate or theme!

In the next demo, the select dropdown acts as a progressive enhancement. The skateboard's theme will update based on the option selected. The following is a simplified example:

:root {
  --color: black;
}

body:has([value="lightning"]:checked) {
  --color: yellow;
}

body:has([value="holiday"]:checked) {
  --color: green;
}

Open CodePen demo

This project has always been one of my personal favorites. It brings me a fair amount of joy seeing it working fully in Firefox.

Helpful resources

Back to all blog posts