The Simplicity of Specificity
Okay, I get it. "Specificity" is a big word. It also seems like a really complicated concept, and might come off to many of you as unnecessary at best, an unexpected nuisance at worst. This raises the question:
Do we actually need specificity in CSS?
Yes. We do.
You're probably not convinced. Guess I'm going to have to get specific about why we need specificity. To do that, I want you to completely clear your mind about what you think specificity is. Let's start tabula rasa.
What is specificity?
Specificity is how specific a rule is. I know that sounds tautological, but it's as simple as that. And if you think about it, it makes sense. If someone tells you to get a goat, they aren't being very specific. There are many types of goats, and they're all fantastic. Now, if someone tells you to get a black goat, they're being more specific about what they want. Or even a white Irish goat with two horns. Now that's getting really specific. That's someone who really knows what kind of goat they like.
But specificity is really complicated/hard/weird to calculate.
No, it's not. If you can count to 10, you can calculate specificity. And hopefully, you'll never have to count higher than 5, tops. Specificity only seems difficult to calculate because the way many sources teach it take into account all the possible ways you can form selectors.
Let's say we have an awesome developer friend who's styling all of our goats. She might start off with styling a div
, but that's not very specific! There are a finite number of element types, such as div
or span
or section
or p
, they show up everywhere in our HTML, and they're pretty limiting.
Okay, she says she'll make it clear that we're styling goats by using the selector .my-goat
. Now we're getting somewhere. We'll say that her original selector belongs in the "not-really-specific" group, and that the .my-goat
class selector belongs in the "pretty-specific" group. You might see where we're going with this.
// specificity: 0 0 1
div {
// styles...
}
// specificity: 0 1 0
.my-goat {
color: white;
// styles...
}
Whoa, what's up with those numbers! I'm bad at math!
Don't worry! Remember our groups? From most-specific to least-specific (left to right), we have three groups:
- Really, really freakin' specific
- Pretty specific
- Not that specific
You just add up the selectors that belong into each category, and you get your specificity. Classes are pretty specific, but elements (types) are not that specific. And, you probably guessed it, IDs are really, really freakin' specific. So specific that only one of them exists in our HTML document. So, #this-exact-goat
is definitely more specific than any number of class selectors for describing which goat(s) we're talking about.
Alright, our developer assumes that goats are generally white, which is why she applied the declaration color: white;
to our .my-goat
class. Fair enough; when we think of goats, they're usually white. But what if I wanted to refer to a black goat?
// specificity: 0 2 0
.my-goat.-black {
color: black;
}
Okay, looks just like our original class. Except now, she's given us two classes: .my-goat
and .-black
. Obviously, if she's giving us two things (classes) to describe a goat, she's getting pretty specific about that kind of goat. Notice that we now have two pretty specific ways of selecting our goat, from those two classes. Even if you're not good at math, it seems clear enough that 0 2 0
beats 0 1 0
.
* By the way, I'm using the RSCSS naming system. You should check it out; it's really intuitive! You can easily adapt this to BEM or whatever, though. I guess.
Now here's the great thing about this: The source order of these rules doesn't matter! My more-specific goat can show up before or after my less-specific goat, and I'll still get exactly what I expect. This is why our developer friend is awesome. She's making our CSS explicit, flexible, and reusable.
Specificity goes even more flexible than that, while staying intuitive. Let's say you want to describe a goat that's inside a house. You're still being specific about what kind of goat you're targeting, so .my-house > .my-goat
(specificity 0 2 0
) is still going to be more specific than just describing a plain goat.
Or what if you're describing a black goat that's being hovered? Like, .my-goat.-black:hover
? The specificity here is now 0 2 1
, because describing the state of something being :hover
ed isn't very specific. It's in the same category as just a plain element type - lots of things can be :hover
ed. Still, you're being slightly more specific than just describing a black goat.
Things you should know
I'm two beers in, so it's time for some real talk. If you're still telling yourself that source order matters, and specificity is still complicated, and you're worried about overwriting styles, and you need to add weird selector hacks to get to the :root
of the problem, and...
I want you to take a moment and forget everything you know about CSS. Keep an open mind.
Okay, not everything. But these things:
Selectors are like global variables.
No, they're not. CSS is a declarative, domain-specific language for styling stuff, not a programming language. Stop thinking of them as variables. They're selectors that say "hey, apply these declarations to these elements." It's like JSON for styles, and no one is saying that JSON is full of global variables. Heck, you can even write stylesheets as JSON objects (but you shouldn't. Seriously.)
CSS is weird because it doesn't behave like
<insert imperative code here>
.
Again, CSS is not a programming language. And even if it were, it most definitely isn't an imperative language. (But how cool would CSS as a declarative, functional language be?) If you're coding CSS as "do A first, and then do B", you're doing it wrong. CSS is more like "A is this, and B is this."
I can override styles of the same specificity by declaring them later in the stylesheet.
Yeah, but you shouldn't. Well-architected declarative CSS is not source-order dependent. If two things of the same specificity have conflicting styles, then something is wrong in the way you're defining styles or marking up classes. You wouldn't have a <div class="my-goat -black -white">
The goat is either white or black! Unless the goat is black and white, then you really ought to have a <div class="my-goat -black-and-white">
class. Be smart about your classes.
Using
!important
is bad.
Not always. The !important
keyword, while it should be seldom used, is useful for semantically important declarations, and even more importantly, for user accessibility (but we won't get into that). If you have a general class .is-hidden
, then chances are that you want whatever has that class to be hidden, no matter what. So even if it has specificity of 0 1 0
, if the declaration is visibility: hidden !important
, we can rest assured that our .goat.-black.is-hidden
will be hidden, no matter what.
I'm constantly fighting with specificity.
Why? Are your selectors crazy long or complicated? Do you see things like ul > li span.link > a:hover
all over your stylesheet? Here's a general rule of thumb: Be only specific as you need to be, and be semantic. We can easily simplify that crazy selector to something like .item-link:hover
. That's the least specific we need to be.
For further inspiration on simplifying your selectors, imagine someone telling you where their house is. If they said something like "It's 1.4 miles down the two-laned street named Some Street that has an unusually long red light, and then the third house to the left with brown shutters and a green lawn and a mailbox," you'd probably ask them why they didn't just give you an address. Be as simple as possible.
So, what if specificity didn't exist?
Well, then you'd basically be writing some imperative/procedural language where the order of things matter, and you'd have code that's much harder to reason about because:
- It's not modular...
- It's not easily reusable, and...
- It's much less flexible, because source order matters.
Just, please, be glad that specificity exists. It serves a very good purpose in our humble declarative styling language that some of us love to hate, and some of us are weird enough to love, CSS.
This is in response to this article.