#292: A Short History of Specificity

[Robin]: Sometimes CSS can feel like everyone is shouting…

.overly#powerful .framework.widget {
  color: maroon;
}

Ah! Please stop yelling at me!

Whenever I read stuff like this I think ah, yes, you are trying to override some other CSS in some other file that has high specificity and you have resorted to just screaming at the browser in order to fix it. It sure works and all, but it’ll lead to problems down the line; fragile code, tech debt, etc.

Manuel Matuzovic made a fabulous demo the other day showing just how complicated specificity can get. Clicking the “Add Selector” button on the page changes the background of his website, and shows precisely why #body:is(#body, #bla#bla) overrides something like #body#body.

Up until very recently, !important was one of a small batch of tools we had to control cascade power. Is someone else writing CSS in some other file and it’s doing something wacky to your component? Just slap an !important at the end and it’ll always override code elsewhere:

body {
  background: red !important;
}

The problem here is that folks would abuse !important and use it all over the place. However! Some folks like Harry Roberts began arguing that we ought to use !important in some very limited cases but to never use it in anger:

Using !important reactively is the most heavy-handed, nuclear, all-the-way-up-to-11 option we have at our disposal. Using !important to get yourself out of a problem with some existing CSS is most certainly inadvisable. It will have knock-on effects whose only solution will be to use another !important, then another, then another, ad infinitum.

We started inventing methodologies (like “Block Modifier Element”) in a desperate way to control how the cascade works, too. A few short years later and CSS Modules appeared on the scene, at the time I remember thinking it was the perfect situation to all these problems: you write a CSS class like .button, run it through a compiler of some kind, and out the other end it would change the class name of the HTML and the CSS to something like .button30587039856 which means that no one could write code elsewhere that would hurt your lovely little component.

The specificity problem was solved!

Well, not really. CSS Modules meant that you needed to have a particularly unwieldy dev environment to get this all working. It required kind of a lot of setup in order to let you fight the way CSS was designed: let some things overwrite other things. For years people wrote about how the very idea of the cascade was foolish, poorly designed, imagined for a world of text documents instead of large and serious web applications.

The year is 2022: enter CSS Cascade Layers. Last week we published Miriam Suzanne’s Complete Guide which is excellent…

Many of us have been in situations where we want to override styles from elsewhere in our code (or a third-party tool), due to conflicting selectors. And over the years, authors have developed a number of “methodologies” and “best practices” to avoid these situations — such as “only using a single class” for all selectors. These rules are usually more about avoiding the cascade, rather than putting it to use.

Yes! Ahem, sorry for interrupting you, Miriam. Please continue…

Using the @layer at-rule and layered @imports, we can establish our own layers of the cascade — building from low-priority styles like resets and defaults, through themes, frameworks, and design systems, up to highest-priority styles, like components, utilities, and overrides. Specificity is still applied to conflicts within each layer, but conflicts between layers are always resolved by using the higher-priority layer styles.

Let’s take a look at an example…

@layer generic, components;

First, we define the order of the layers. CSS in the “components” layer will override the styles in the “generic” bucket and each item you add in this list will make those styles move up higher in terms of specificity.

Next, we can define those layers, or buckets, like this:

@layer generic, components;

@layer generic { 
  body {
    background-color: tomato;
  }
}

@layer components {
  body {
    background-color: lightseagreen;
  }
}

Now, one thing I didn’t really understand with Cascade Layers is how it changes our relationship with !important. Let’s say you have two cascade layers like the ones above: generic styles (less important) and components styles (more important).

So what happens when we use !important like this?

@layer generic, components;

@layer generic { 
  body {
    background: blue !important;
  }
}

@layer components {
  body {
    background: red !important;
  }
}

What is the background color of our body element here?

If you asked me before I read Miriam’s post I would’ve said “ah, what a foolish question! How silly of you! Of course, old chap, components styles will win out and the background of the body element will be red. Because we’ve written @layer generic, components; it means that everything in components will surely win.”

Nope, dead wrong: the background will be blue!

At first, this doesn’t make much sense. You’ve told the browser, yo, “components” are more important than “generic” styles. But one odd quirk is that when you use !important in “lesser” important layers (like generic above) then those styles will always win instead. The order of importance is flipped when you use !important and this sort of makes sense the longer you think about it: you want some styles that are lower down in the layers stack to always take precedence.

What does all this mean in practice? I think we need to be much more careful with how we use !important in this new world because it could confuse a bunch of folks at first. The good news (I think) is that by introducing @layers into our CSS means we’ll rarely need to use !important from here on out anyway. Here’s what Miriam says:

Once we start using cascade layers, we will need to be much more cautious and intentional about how we use !important. It’s no longer a quick way to jump to the top of the priorities — but an integrated part of our cascade layering; a way for lower layers to insist that some of their styles are essential.

Overall, I think the interesting thing about @layers is how it lets us control specificity at this very minute level that we’ve never been able to before. And for most day-to-day work I can imagine that @layers might not be necessary at all. But for large and complex app-like websites? I can see them being essential; layers are a powerful tool to make sure some styles stay in their dang lane.

Just watch out for !important.


Synthetic testing: A definition and how it compares to Real User Monitoring

Performance monitoring is critical for a healthy software application. If you don’t have synthetic testing or real user monitoring in place, opportunities for performance optimizations are slipping through the cracks.


[Chris]: Thanks Robin for that interesting trip through Cascadeville. I’m thinking that the existence of Cascade Layers is going to (collectively) get us thinking much more about how styling is applied. It’s interesting how specificity, a thing a lot of us have spent a career focusing on, is just one step of bigger picture of the cascade. With more control over more aspects of it, little micro battles in specificity land, I hope, will be more rare. We’ll flex cascade muscle in different, and likely more logical places.

If you can’t get enough of this stuff, or learn better through video, I’d recommend watching Bramus’ Understanding the Cascade presentation he gave recently.

Ya know, I’ve had to shed my own confidence in understanding this stuff. I’d like to think I have a solid understanding of specificity, but when you zoom out at all the rest of it, I’m much more shakey. Just Robin’s example above about how !important declarations are reversed and lower-layers actually have more power is not something I would have guessed and, while I think I like it, feels a little foot-gun-y, and I won’t be able to apply confidently until I work with this stuff a lot more. Oh hey, speaking of !important, Una has a great video on it recently you should check out. She makes the point that this “inversion” of !important layering isn’t brand new to Cascade Layers, it exists today. For example, if a browser’s User Agent stylesheet (normally the least important thing we have to deal with) has an !important declaration, it will override everything, even our own !important declaration, or a user-applied one. Weird right?!

Here’s to learning new things!