Some little ways I’m using CSS :has() in the real world

I’ve created some low fidelity demos of :has() snippets that I’ve been using in real-world client projects.


There’s a lot of chatter around the new(ish) :has() pseudo-class. It’s something we’ve been crying out for, for years: being able to select parent elements!

A useful mental model for :has() is that you are querying the parent’s children’s state and/or presence rather than selecting the parent from the children themselves. I like that. It makes a lot of sense.

I’m not 100% convinced :has() is the silver bullet others might claim it is though. I personally still utilise CUBE exceptions more regularly, but I am also in the privileged position where projects I work on in the studio don’t restrict access to the markup. I see :has() as being more useful for little tweaks more than anything, but if you don’t have access to markup, it really is a silver bullet.

With all that in mind, I thought I’d produce some low fidelity examples of how I’ve been using :has() lately on proper client projects to give you some real world stuff to look at. Let’s dig in.

In a design system we work on for a client, there’s a pretty straightforward banner. This was recently updated to be dismissible, so we had to create a new variant to the pattern.

The only difference to the default pattern though is there’s a <button> element present, so a quick :has() query later, we could apply a flex layout in a jiffy.

Code language
css
.banner {
  background: var(--color-primary);
  color: var(--color-light);
  font-weight: var(--font-bold);
  text-align: center;
}

.banner:has(button) {
  display: flex;
  justify-content: space-between;
  gap: var(--space-s);
  text-align: revert;
}

See the Pen Banner layout demo by Andy Bell (@piccalilli) on CodePen.

Flex labels with input children permalink

The context for this is that I like the following pattern for labels because I like to keep them as inline elements, but form fields that follow them should break on to a new line.

Code language
css
label::after {
  content: "\A";
  white-space: pre;
}

For labels that contain inputs like checkboxes and radios though, it’s useful to render those as flexbox layouts. Historically, that would require a class being added (or several in ASS codebases), but now, I’ve updated our global styles to this instead.

Code language
css
label:has(input) {
  display: flex;
  align-items: flex-start;
  gap: var(--space-s);
}

See the Pen A label with text input followed by a label containing a checkbox by Andy Bell (@piccalilli) on CodePen.

Highlight parent elements when their children are targeted permalink

This one is super quick and super simple. If you’ve got an element with an id, you can trigger its :target state by appending it’s id to the URL with a #, like this: https://example.com/#my-element.

Historically, you couldn’t apply styles to an element’s parent when it’s targeted, but now you can with :has().

Code language
css
section:has(:target) {
  background: var(--color-light-shade);
  border: 2px solid var(--color-primary);
}

Handy as heck.

See the Pen A section is highlighted when its heading is targeted by Andy Bell (@piccalilli) on CodePen.

Dimmer siblings when an element is hovered permalink

It’s a design pattern that’s been on the web forever. The idea is when you hover an element its siblings all dim, so the user’s visual focus is more targeted.

We’ve been able to do this with CSS forever too, but the selectors to achieve the effect were pretty gnarly. Quite a few approaches resulted in flickering or all items dimmed if your pointer accidentally found itself in gutters too.

It’s not the case any more with :has() though!

Code language
css
.tiles:has(:hover) .tile:not(:hover) {
  opacity: 70%;
}

The beauty of this selector is it’s really clear what’s going on too.

See the Pen Other tiles dim when one of them is hovered by Andy Bell (@piccalilli) on CodePen.

Wrapping up permalink

Yeh, there’s nothing complicated or fancy in this article, but I just wanted to show some handy real-world ways to use :has(). If you really want to get into :has(), I strongly recommend checking out Ahmad’s interactive guide. It’s fantastic!

P.S. one last little trick. On this site, paragraphs in the .post block are limited to 60ch as a max-width. That’s not ideal for demos though, so…

Code language
css
.post p:has(code-pen) {
  max-width: unset;
}

Easy peasy 🙂