24

CSS :has( ) A Parent Selector Now · Matthias Ott – User Experience Designer

 2 years ago
source link: https://matthiasott.com/notes/css-has-a-parent-selector-now?ref=sidebar
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

CSS :has( ) A Parent Selector Now

I don’t remember the exact moment anymore. But I remember that it was with a mix of disbelief and disappointment that I realized one day that there was no way to select the parent of an element in CSS.

Wait, what? This can’t be. Why?

Obviously, I wasn’t alone. Everyone who has ever learned CSS has probably found themselves in the same situation and asked themselves the same question: Why is it that we don’t have a parent selector in CSS? As Jonathan Snook described in an elaborate post back in 2010, it was not only because of performance concerns but also because of the way browsers render a document and apply the computed styles to elements: as a stream, one element after the other as they come in. When an element is rendered to the viewport, its parent is already there, including all its beautiful styling. Repainting the parent – and, consequently, all other parents – after the fact would then require another evaluation of all parent selectors. This, Remy Sharp agreed back then, would again be so expensive on the render engine that it wouldn't make sense to implement a parent selector.

So, we all accepted the inevitable and did our best to work around it. We added helper classes to our templates via back-end logic or with JavaScript in the frontend. And it somewhat worked. But in almost every project, there still was this one moment where a parent selector would have been the quickest and most “materially honest” solution.

:has has arrived

All of this changed last month when Apple released Safari 15.4 which, as the first browser, supports the :has() pseudo-class, which is one of the CSS Level 4 Selectors. “It was long thought such a selector was not possible, but our team figured out a way to highly optimize performance and deliver a flexible solution that does not slow the page,” Jen Simmons and Jon Davis wrote in the release notes.

There it is. A parent selector. But :has is not only useful as a parent selector. It also opens up a lot more interesting opportunities. But first, let’s have a look at how it works.

The :has pseudo-class takes a relative selector list and will then represent an element if at least one other element matches the selectors in the list. Sounds overly complicated when you write it down, so here is a basic example. Let’s say you want to select all button elements which include an svg element, like an icon. You can now write:

button:has(svg) { … }

Try the CodePen demo – and make sure to view it in Safari 15.4. ;)

Or, if you want to style an article differently if it has a headline inside that is followed by a paragraph:

article:has(h2 + p, h3 + p) { … }

You can also combine :has with the :not pseudo-class to select an element if it does not contain certain other elements, in this case, a headline:

section:not(:has(h1, h2, h3, h4, h5, h6)) { … }

It is also possible to use pseudo-classes like :hover, :focus, or :checked. You could style a form element when a checkbox inside has been checked, for example:

form:has(input[type="checkbox"]:checked) { … }

You could then also use the general sibling combinator ~ to change the styling of the form depending on how many of the checkboxes have been selected:

/* Two checkboxes are :checked… */

form:has(input[type="checkbox"]:checked ~ input[type="checkbox"]:checked) { 
  box-shadow: inset 0 0 0 0.5rem blue;
  background-color: rgb(150, 187, 235);
}

/* Three checkboxes are :checked… */

form:has(input[type="checkbox"]:checked ~ input[type="checkbox"]:checked ~ input[type="checkbox"]:checked) { 
  box-shadow: inset 0 0 0 0.5rem rgb(0, 160, 0);
  background-color: rgb(136, 233, 136);
}

Try the CodePen demo.

Although this demo might not be the most practical example, all this is still starting to get interesting, right?

Using :has to adjust a Grid

CSS Grid generally works from the container in and before :has, there wasn’t really a way to adjust the Grid based on what’s inside. What if we could use :has to adjust the CSS Grid layout of a container based on the number of elements inside of it? Here, so-called quantity queries can be useful. Heydon Pickering has written the ultimate article about them. By combining the :nth-child and :nth-last-child pseudo-classes inside our :has selector list, we can adjust the layout by “counting” the elements inside. Here is how we can target a grid with two elements inside, for example:

.grid:has(:nth-child(2):last-child) {
  grid-template-columns: 1fr 1fr;
}

So, if our Grid container has an element inside that is both the second child as well as the last child of our container, the grid should have two columns. The same also works with three columns:

.grid:has(:nth-child(3):last-child) {
  grid-template-columns: 1fr 1fr 1fr;
}

Finally, we can also create a grid layout that has four columns if the container has four or more elements inside. To achieve this, we select at least one element that is either the fourth or one of all following elements:

.grid:has(:nth-child(n+4)) {
  grid-template-columns: repeat(4, 1fr);
}

Here is a CodePen demo again.

As you can see, there are lots of amazing use cases for :has as a parent selector and it still feels like this is only scratching the surface of what people will come up with. :has lets us style elements based on whether the child of an element is selected, has focus, or whether it is present at all. And we can even target an element based on how many children it has. All this will allow for much more flexible components. Just think of all the variations of content and context that can exist inside a design system and you know how valuable this will be.

But wait, there is more!

:has as a previous sibling selector

Because :has accepts a relative selector list, you can also select an element if it has another element that follows it. Or, in other words, we can use :has to select the previous sibling of an element:

:has(+ .second) {
  background-color: darkmagenta;
}

Try it on CodePen.

Again, there are so many practical use cases for this! For example, you could style an image differently when there is a caption below it:

figure img:has(+ figcaption) {
  /* Do something with the image */
}

The possibilities of using :has in useful and creative ways to write more flexible and robust CSS seem endless. And as is the case with so many of the new features that are currently being added to CSS, I can't wait to see what other creative uses for :has you all come up with. One recent example is this brilliant demo by Michelle Barker, where she combines animated grid tracks with the :has selector.

Testing for support

For now, Safari is the only browser with support for :has. In the upcoming version 101 of Chrome, you’ll be able to activate it via the Experimental Web Platform features flag, however. While this is promising, it will certainly take a little while until :has is supported by the majority of browsers. In the meantime, we can once more use progressive enhancement to already use :has in browsers that support it. As Michelle Barker notes, you can test for support for :has with a @supports feature query using the selector() function:

@supports selector(:has(*)) {
  /* Code for browsers with support for :has */
}

Thus, you can target supporting browsers in a future friendly way. Because once :has is supported by another browser, your code will work automatically in this browser as well.

Do you have more ideas about how to use :has? Have you seen more interesting examples and demos? And will you start using it already? Let me know via Webmention, email, or Twitter.

Links


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK