Lowering the specificity of multiple rules at once
posted on
You probably already knew that you can use :where() to lower the specificity of a single selector, but did you know that you can achieve a similar effect on multiple rules at once?
The other day, I had an Aha! moment when someone suggested something I knew existed but hadn't considered using. In my reset style sheet UA+, I used :where() for combined selectors to keep specificity low. Here's an example:
:where(abbr[title]) {
cursor: help;
text-decoration-line: underline;
text-decoration-style: dotted;
}
Someone suggested using it everywhere for consistency and to achieve the lowest possible specificity for all rules. I liked the idea and wrapped all selectors in :where().
:where(h1) {
font-size: 2em;
margin-block: 0.67em;
}
This change improved usability but reduced readability.
It was like that for a while until Emilio asked why I didn't wrap everything in an anonymous @layer instead. When I read his question, I needed some time to process because the solution felt so obvious, yet I was surprised I hadn't thought of it.
@layer {
abbr[title] {
cursor: help;
text-decoration-line: underline;
text-decoration-style: dotted;
}
h1 {
font-size: 2em;
margin-block: 0.67em;
}
}
Here are the things that went through my head when I saw Emilio's post:
Using an anonymous layer without any other cascade layers
Let's say you don't have any cascade layers in your CSS. By adding a third-party file containing all rules nested in a layer, all your rules will override the rules within that layer, since unlayered rules have precedence over layered ones.
/* uaplus.css */
@layer {
h1 {
color: red;
}
}
/* your.css */
h1 {
color: blue;
}
/* Result: blue */
Of course, that's only the case as long as I'm not using !important in UA+.
What's cool about this is that it doesn't even matter where or how you include the reset stylesheet. Normal unlayered rules always override normal layered rules.
/* your.css */
h1 {
color: blue;
}
/* uaplus.css */
@layer {
h1 {
color: red;
}
}
/* Result: blue */
Using an anonymous with other cascade layers
If you are already using cascade layers, there are two things we need to take care of: order and naming.
Order
For this to work, the anonymous layer must be the first layer in the CSS, since layers defined later in the document override those defined earlier.
/* uaplus.css */
@layer {
h1 {
color: red;
}
}
/* your.css */
@layer base {
h1 {
color: blue;
}
}
/* Result: blue */
Since a reset style sheet is something that you put at the very beginning of your CSS anyway, that shouldn't be a problem.
Naming
My layer is anonymous to avoid clashes with your layers. That's important because I can't ensure you're not using a name like “reset”, for example. Using a named layer could potentially mess with the order of your layers.
In the following example, my reset stylesheet would change the order of layers from first, reset, base, components to reset, first, base, components, if I named it reset.
/* fictional-uaplus.css */
@layer reset {
h1 {
color: blue;
}
}
/* your.css */
@layer first, reset, base, components;
Beyond reset stylesheets
Writing this post, I realised that this technique is not just limited to resetting stylesheets. If you have a specificity issue within a layer, you can wrap rules in an (anonymous) layer to regain control.
@layer components {
h1 {
color: blue;
}
@layer {
h1 {
color: red;
}
}
}
/* Result: blue */
I really like this solution, and I hope you had a similar Aha! moment reading my post as I did reading Emilio's comment (Thanks again). :)