CSS :where() to replace complex multi-selectors

We all know these endless lines of CSS selectors before the real CSS rules start in the stylesheet. And while it’s not entirely new anymore, I’ve not seen it much in the wild: The usage of the universal :where() selector.

So we’re coming from the following example where I want to style elements in an text article different to how the same elements are styled globally on the site. Usually you write it that way, repeating the whole selector chain for every single line:

.text h2,
.text h3,
.text h4
.text hr,
.text legend {
    margin-top: 1ch;
    margin-bottom: 0.5ch;
}

Today, we can use this instead:

.text :where(h2, h3, h4, hr, legend) {
    margin-top: 1ch;
    margin-bottom: 0.5ch;
}

This is already much cleaner and better to read but see this example to get a better idea of how much of an improvement it is:

.foo > header > h2:hover::before,
.foo > header > h2:focus::before,
.foo > header > h3:hover::before,
.foo > header > h3:focus::before
.foo > header > h4:hover::before,
.foo > header > h4:focus::before {
    margin: 1.25em 0;
}

At least I often have a situation that needs me writing such or even more complex selectors that repeat a lot of stuff. The result is that I rely a lot on the search functionalities of my code editor. But with the new universal selectors it turns into this:

.foo > header > :where(h2, h3, h4):where(:hover, :focus)::before {
    margin: 1.25em 0;
}

Unknown rules are not entirely discarded

Now one more thing: :where() and :is() both are forgiving selectors. This means that even if a selector inside isn’t supported by the browser or unknown/invalid, it will still work for the rest. And this means we turn the following into a one-liner as well:

.form-element focus {
     border-color: red;
}

.form-element :focus-within {
    border-color: red;
}

Until now, we had to write both in a separate selector statement. That is because if a browser does not understand some part of a selector statement it turns the whole statement invalid and discards it. But as the new universal selectors are different we can simplify here as well:

.form-element :where(:focus, :focus-within) {
    border-color: red;
}

It helps avoiding !important

Instead of :where() you can also use :is(). The difference is that :where() does not count into selector specificity while :is() does increase specificity. With that knowledge, it’s one more tool to avoid unnecessary !important statements.

Browser support & Credits

Now that’s pretty cool, isn’t it? Here’s more info:

Thanks to Wes Bos Tweet for inspiring me to write this up as a note here.

Written on as Note