What's new in CSS?

:has() Pseudo-class

The :has() Pseudo-class

This humble looking new pseudo-class might just be the most powerful pound-for-pound feature CSS has dropped in years.

It's base functionality is simple - select an element that contains something else, but this utility opens up so many doors and allows so many things to be accomplished in CSS where you would have previously had to reach for JS.

Standard CSS

div:has(p) {
  background: red;
}

Tailwind

<div class="has-[p]:bg-red"></div>

Selecting Containers

The most obvious use case for :has() is of course targeting an element's container.

HTML Markup

<label>
  Option One
  <input type="radio" />
</label>

Standard CSS

label:has(:checked) {
  /* Styles */
}

Tailwind

<label className="has-[:checked]:text-indigo-800 has-[:checked]:bg-indigo-50 ...">
  Option Two
  <input type="radio" />
</label>

Selecting Previous Sibling

We've been able to select adjacent siblings that follow an element for a long time now with the + and ~ combinators, but we can now use these to select adjacent siblings that come before an element!

We can now easily select elements that come BEFORE another element!

Random demo image from picsum CDN

(And we can still easily select elements that come after too of course!)

Standard CSS

/* Target p after an img */
img + p { font-weight: bold; }
/* Target p before an img */
p:has(+ img) { font-weight: bold; }

Tailwind

/* Target p after an img */
<img class="[&+p]:font-bold" />
/* Target p before an img */
<p class="has-[+img]:font-bold"></p>

Global Conditional Targeting

The :has pseudo-class isn't limited to direct children - you can use it as high up the tree as you want, to the extent that we can use it as a kind of global detector to style something based on the existence or state of something else.

Another common use-case for me is to disable overflow on the body tag if there is a modal or off-canvas menu currently active.

Go ahead and click the menu button in the bottom left of this page, and you will see the body can no longer be scrolled - without any JavaScript!

body:has(.sidebar) .any-element-on-page {
  /* styles */
}

Standard CSS

body:has(.slideout-menu-active) {
  overflow: hidden;
}

Tailwind

<body class="has-[.slideout-menu-active]:overflow-hidden">

The scope is huge!

The possibilities are endless with :has() - you could feasibly use it to create any number of interactive components, but remember, in many cases JS will be the more appopriate tool for the job!

I'm some content for tab one.

I'm some content for tab two.

I'm some content for tab three.

Standard CSS

.tabs:has(.tab1:checked) .tab-panel1,
.tabs:has(.tab2:checked) .tab-panel2,
.tabs:has(.tab3:checked) .tab-panel3 {
  display: block;
}

:has() Pseudo-class

Menu