Josh Brown

February 6, 2024

CSS Specificity: A Quick Primer

Yesterday I wrote a bit about maintainable CSS, today I'll keep up the trend of CSS with a little post digging into specificity.

One of the great, but often misunderstood aspects of CSS is specificity. And it's something I ran into today.

What the heck is specificity?

Specificity is one of the elements that affects the order in which our rules get applied. The more specific your selector, the harder it will be to override it's rules.

You might already know that id's are the most specific, followed by classes, and finally the elements themselves like div, span, p, label, etc.

If you're using a chromium based browser like Chrome, or Brave then you can hover on the class name in the styles inspector to find it's specificity value. Specificity is made up of three separate numbers, you can think of this like a version number. 0,2,0 is greater than 0,1,5.

You may often find yourself running into an issue where your styles won't apply because a more specific rule is already applying to them. You know that  !important  will fix the problem. But, you feel dirty about it - and, so you should! What other options are there though!?

Let's say we're applying an error state to a label, we want the label to turn red but another rule is already applied to the label.

.field label sets the color to black, and has a specificity of 0,1,1

We want the label to be red so we apply the class .text-red-500, which has a specificity of 0,1,0

Oh no! It's not specific enough, what can we do?

Increasing the specificity

The first answer is typically to increase the specificity of the rule you're applying, but often this is the wrong approach.

Sure, maybe we could add some more CSS to our field label to handle this particular exception to our component/block. .field.error label with color set to red does the job with it's specificity of 0,2,1. But, now we have more code to maintain.

This may be worth the trade-off if our error styling is particularly specific and composed of a large handful of different rules. In our case though we just need the label to turn red.

Decreasing the specificity

The real issue at hand here isn't that our .text-red-500 class needs to be more specific, but that our `.field label` selector was too specific in the first place.

But how can we make our rule less specific? Of course, we could assign our label a class, but then we have to apply that class to every label in the context of our field, that's a lot more work. Sure we could write an abstraction to apply that class but now we have even more code to maintain! How else could I select the label of the field?

Well, actually there are many ways, let's review some and check their specificity values:

.field label = 0,1,1
.field :is(label) = 0,1,1
.field :where(label) = 0,1,0
:where(.field) label = 0,0,1
:where(.field label) = 0,0,0

Screenshot 2024-02-06 at 11.51.29.png
(Generated with https://specificity.keegan.st/)

All of these selectors select the exact same element, a label nested somewhere within a .field element. But they all get their specificity calculated in different ways.

:is will take the specificity of its most specific argument, it can take multiple arguments to select multiple elements, since we only pass one argument the specificity is unchanged.

:where has no value for specificity so anything arguments within will not count towards the specificity of the selector.

You'll likely always want some level of specificity otherwise you won't be able to change the default browser styles! But knowing these selectors and how they work will mean you can think more carefully about how specific you're applying your rules.

In our case both .field :where(label) and :where(.field) label both likely work fine, I personally would probably opt with the first giving us an equal specificity value to our utility classes.

What happens to selectors with equal specificity?

There's a lot more that happens than I can fit within the scope of a single article about the CSS Cascade, though as a basic rule of thumb rules that appear later override those that appear earlier. Define your generic base styles and components at the top and your utilities towards the bottom.

If you're keen to learn more I really recommend checking out the MDN Web Docs on CSS Cascading Order. There's lots more techniques you can apply including layers, and scopes.

Next time you're wondering why your rule isn't applying, I suggest checking it's specificity, there's a really useful calculator online that let's you compare specificities: https://specificity.keegan.st/

About Josh Brown

Aspiring polymath; designer, developer, writer, maker, creator, gamer and aviator.

Want to chat? Drop me an email, or follow me on Twitter