DEV Community

Cover image for Getting Specific About CSS Specificity
Kathryn Grayson Nanz
Kathryn Grayson Nanz

Posted on

Getting Specific About CSS Specificity

You likely already knew that some methods of styling will override others when you’re writing CSS...but do you know why?

For example, a style assigned to a class will be preferred over one applied to a base element. Here, the header with the class name will be red, even though we said h2s should all be blue. That’s because CSS weighs classes as more specific than elements, and therefore prefers the red style defined to the class.

<style>
h2 { color: blue; } 
.my-header { color: red; } 
</style> 

<h2 class="my-header">Hello World!</h2> 

But there’s more to this than some mysterious rock-paper-scissors game happening somewhere in the browser. In fact, CSS specificity is calculated using points and written using a 4-number notation in which each identifier in a style definition is is tallied. In the 4-number notation, there's a column for inline styles, IDs, classes, and elements.

Let's look at an example:

#nav .menu li a { font-weight: bold; }

Inline Style ID Class Element
0 1 1 2

We would read this specificity level as [0, 1, 1, 2]. That's because there are no [0] inline styles in a CSS file, one [1] ID (the #nav), one [1] class (the .menu), and two [2] elements (the list and the anchor).

When two different styles are applied to the same thing, their specificity levels are compared and the higher one wins. So, in this case, if we wanted to override that example style, we could create another style that added another class [0, 1, 2, 2] – or, we could add an inline style in the HTML [1, 1, 1, 2]. And – for better or worse – !importants will trump everything, no contest.

You may be asking why we don’t just read these as numbers – in that example, one hundred and twelve versus one hundred twenty two or one thousand, one hundred and twelve. After all, if we think about this as a kind of base ten points system, we should just be able to add it all up and read the number from left to right…right?

Unfortunately, it’s not quite that easy. We use this particular notation because sometimes we’ll go over ten in any individual column – like if you were to put twelve classes on something. I don’t particularly encourage writing something that uses that many classes, but but hey – some of you are probably Tailwind users, right? 😉

If we had twelve classes, our specificity might look like [0, 0, 12, 1]. Just reading from left to right, we might be tempted to read that as one hundred and twenty one...but the specificity of something with twelve classes and an element would be different than something that had one ID, two classes, and one element: [0, 1, 2, 1] – which could also, arguably, be read from left to right as one hundred and twenty one. So, the commas are actually in place to discourage us from doing just that.

Specificity with :is() and :where()

Okay, with that background let's look at a fun real-world example with some new(ish) CSS: :is() and :where(). These are two ways for us to write compound selectors with more clarity and less hassle. In CSS, “compound selectors” is just a fancy way for us to say “styles that apply to more than one element.” This is useful for times when we want to do things like apply the same color to all our headers or zero out the defaults on several different base elements.

For a long time, the way we did this was using comma-separated lists, like this:

h1, h2, h3 { font-weight: bold; }

Now, we can use :is() or :where() to do the same thing, with (actually) a very similar syntax:

is:(h1, h2, h3) { font-weight: bold; }

where:(h1, h2, h3) { font-weight: bold;}

What does this have to do with specificity? Well, when we use :is(), the specificity level is determined by the most specific thing included in the list, but when we use :where() the specificity level is always zero.

That means that: :is(h1, h2, h3) has a specificity of [0, 0, 0, 1] – the specificity level defaults to the highest level included in the :is() list. If we wrote :is( #main, h1, h2, h3), the specificity would be [0, 1, 0, 0] because we included an ID.

If we were to use :where() in those same situations – both with and without the ID – the specificity would be [0, 0, 0, 0] in both cases because :where()s specificity level is always zero. That means that, if we’re feeling particularly fancy, we can use :where() to our advantage to manipulate specificity rules in our code – no matter what we put into our :where() list, it won't be counted towards the specificity level of that style rule.

That's your CSS trivia for today! Go forth and wow your coworkers with your knowledge of web dev minutia.

Top comments (6)

 
_ndeyefatoudiop profile image
Ndeye Fatou Diop

Thanks for explaining this in such a concise way 🙏

 
kilianvalkhof profile image
Kilian Valkhof

I built a CSS specificity calculator a while back that you can put your selectors in, and it will show you the specificity and explain how each part contributes to the total.

Check it out: polypane.app/css-specificity-calcu...

 
kathryngrayson profile image
Kathryn Grayson Nanz

This is really, really cool! I wish I had known about it earlier, and it's absolutely getting bookmarked for future use now, haha

 
nadeem_zia_257af7e986ffc6 profile image
nadeem zia

Good explanation

 
rex_solomon_e9d369addaeaf profile image
Rex Solomon

Thank you

 
haidarusman17_0f515502047 profile image
haidarusman17

thanks for sharing

Some comments may only be visible to logged-in visitors. Sign in to view all comments.