Bruce Lawson's personal site

CSS :has(), the God Selector

Probably everybody knows this, but I didn’t and in case you don’t, here’s why the CSS :has() pseudo-class is omnipotent and so awe-inspiring I call it the God Selector.

I was working on a no-JavaScript fallback for a mobile hamburger menu, using the newly-baseline HTML Popover API. For users without JavaScript (and, yes, there are many), the code looks like this:

<header>
<a><picture> <!-- Logo homelink --> </picture></a>
<button> <!-- big fat CTA --> </button>
<search> .. </search>
<button popovertarget="narrowscreen-nav">Menu</button>
<nav id="narrowscreen-nav" popover> ... </nav>
</header>

I wanted to give the button a different style if its associated popover is shown. There is no pseudo-class for button:pressed (which is why some people like the checkbox hack, which is clever but has the wrong semantics. And, anyway, popovers can’t be triggered from inputs).

There is, however, a pseudo-class to tell you if a popover is open: :popover-open. Now, to find a way to style the button element by targetting this aspect of its associated (but not descendant) popover target element.

Sometimes people called :has a “parent selector”, but it’s not only that. I knew that it could “reach out” and look at siblings etc if there is the necessary CSS combinator. This selector worked for my code:

button:has(~nav:popover-open) {background-color: goldenrod;}

However, I wasn’t happy about it; what happens if the nav ceases to be a sibling (as it soon did, because I needed a wrapper around the stuff before the nav, to lay it out using Flexbox)? I tend to dislike handing code over to clients that has a very tight dependancy on exact markup structure.

Luckily, I had a conversation with the splendid Luke Warlow of Igalia (a free software consultancy/ hippie commune who do loads of work in all the browser engines), and he blew my mind. It turns out I was thinking about :has the wrong way round.

Behold the almighty God Selector:

:has(nav:popover-open) button[popovertarget] {background-colour:goldenrod;}

This is omnipotent because it doesn’t require any structural relationship between the thing being checked and the thing being styled. The first bit of the selector :has(nav:popover-open} doesn’t have a class, ID or element name, so is scoped to the root of the document. That’s the thing being checked: is there a nav that is an open popover anywhere in this page?

The second part of the selector is the thing to be styled, which is a descendant of the root – and, of course, everything on the page is a descendant of the html element, which is the root. Think of the selector as html:has(nav:popover-open) button[popovertarget] if that makes it easier to reason about.

Of course, you can narrow it down to hone in on the exact nav or button you’re interested in. For example,

header:has(nav:popover-open} button.letsBeSpecific[popovertarget] {background-color:goldenrod;}

And now, I’ve used the God Selector in three different places in the same stylesheet already, to achieve an effect I could not have done without handing over markup of much greater fragility. Thank you to the Mighty Luke Warlow, and I’ll have a kilo of whatever goes in the Igalia bong.

Buy "Calling For The Moon", my debut album of songs I wrote while living in Thailand, India, Turkey. (Only £2, on Bandcamp.)

2 Responses to “ CSS :has(), the God Selector ”

Comment by Luke Robinson

Thank you for sharing this awesome selector. It’ll be very handy in future projects.

I noticed a British spelling snuck into your CSS property, background-colour 🙂

Leave a Reply

HTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> . To display code, manually escape it.