And we've come to the selector that every CSS developer has hoped for years, the :has
selector. Here's how I usually use it.
Example 1: making anchor a block if it contains an image
Let's say you have an image that is wrapped in an anchor. The image's display
property is inline
by default. Now, you can change it to block
globally by targeting all anchors with an img
element.
a:has(img) {
display: block;
}
Example 2: adjust the layout based on elements
Imagine two hero components: one with and one without the main image.
Before the :has
selector, we couldn't style these components differently without adding an extra class to the parent element. Now we can do that with the :has
selector:
.hero:has(.hero__gfx) {
grid-template-columns: auto 1fr;
grid-template-areas:
"gfx title"
"gfx subtitle"
"gfx desc";
}
We don't have to add any extra class to style these two components differently anymore.
Example 3: indicating input
toggle state
Input toggles are not standard input
elements, so developers usually have to develop a custom solution, and that solution often requires JavaScript. But not anymore. Let's see the new example:
The example is simple:
- it uses two radio
input
elements styled as an input toggle, - when the radio
input
is checked, thelabel
is underlined, indicating the active state.
Before the :has
selector, to set the active state, we had to rely either on JavaScript or on a precise HTML structure, which would limit the styling possibilities.
In our example, notice how radio inputs are in the separate div
element used for styling purposes, and label
elements are direct ancestors of the parent .toggle
element.
<div class="toggle">
<label for="check0">Uncheck</label>
<div class="toggle__indicator">
<input type="radio" id="check0" name="check" value="0">
<input type="radio" id="check1" name="check" value="1" checked>
</div>
<label for="check1">Check</label>
</div>
Now, with the :has
selector, we can set the active state of the label
element like this:
.toggle label:hover,
.toggle:has(input[value="0"]:is(:hover, :checked)) label:is(:first-of-type),
.toggle:has(input[value="1"]:is(:hover, :checked)) label:is(:last-of-type) {
text-decoration: underline;
}
If you read the selector out loud, it makes sense:
If the .toggle
element has the input
with a value
of 0
, which is in hover
or checked
state, then the text-decoration
is applied to the first label
element.
Conclusion
I like any CSS selector that helps me write less JavaScript and HTML code. The :has
selector is especially powerful and efficient, so it is my new favorite.