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.
Banner layout adjustmentspermalink
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 piccalilli (@piccalilli) on CodePen.
Flex labels with input childrenpermalink
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 piccalilli (@piccalilli) on CodePen.
Highlight parent elements when their children are targetedpermalink
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 piccalilli (@piccalilli) on CodePen.
Dimmer siblings when an element is hoveredpermalink
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 piccalilli (@piccalilli) on CodePen.
Wrapping uppermalink
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 🙂