CSS Specificity
By Flavio
Learn how CSS specificity decides which rule wins when several target the same element, and how to calculate it across the type, class, and id columns.
What happens when an element is targeted by multiple rules, with different selectors, that affect the same property?
For example, let’s talk about this element:
<p class="dog-name">Roger</p>
We can have
.dog-name {
color: yellow;
}
and another rule that targets p, which sets the color to another value:
p {
color: red;
}
And another rule that targets p.dog-name. Which rule is going to take precedence over the others, and why?
Enter specificity. The more specific rule will win. If two or more rules have the same specificity, the one that appears last wins.
Sometimes what is more specific in practice is a bit confusing to beginners. I would say it’s also confusing to experts that do not look at those rules that frequently, or simply overlook them.
How to calculate specificity
Specificity is calculated using a convention.
Specificity is three components: ID, CLASS and TYPE. People write that as (1,0,0) or 1-0-0.
A common teaching trick adds a fourth slot on the left for inline styles: 0 0 0 0. The examples below keep that 4-digit form. The three right slots are the real score. Inline styles sit outside it, as I’ll say under Slot 4.
The slot at the left is the most important, and the rightmost one is the least important.
Like it works for numbers in the decimal system: 1 0 0 0 is higher than 0 1 0 0.
Slot 1
The first slot, the rightmost one, is the least important.
We increase this value when we have a type selector. A type is a tag name. If you have more than one type selector in the rule, you increment accordingly the value stored in this slot.
Examples:
p {
} /* 0 0 0 1 */
span {
} /* 0 0 0 1 */
p span {
} /* 0 0 0 2 */
p > span {
} /* 0 0 0 2 */
div p > span {
} /* 0 0 0 3 */
Slot 2
The second slot is incremented by 3 things:
- class selectors
- pseudo-class selectors
- attribute selectors
Every time a rule meets one of those, we increment the value of the second column from the right.
Examples:
.name {
} /* 0 0 1 0 */
.users .name {
} /* 0 0 2 0 */
[href$='.pdf'] {
} /* 0 0 1 0 */
:hover {
} /* 0 0 1 0 */
Of course slot 2 selectors can be combined with slot 1 selectors:
div .name {
} /* 0 0 1 1 */
a[href$='.pdf'] {
} /* 0 0 1 1 */
.pictures img:hover {
} /* 0 0 2 1 */
One nice trick with classes is that you can repeat the same class and increase the specificity. For example:
.name {
} /* 0 0 1 0 */
.name.name {
} /* 0 0 2 0 */
.name.name.name {
} /* 0 0 3 0 */
Slot 3
Slot 3 holds the most important thing that can affect your CSS specificity in a CSS file: the id.
Every element can have an id attribute assigned, and we can use that in our stylesheet to target the element.
Examples:
#name {
} /* 0 1 0 0 */
.user #name {
} /* 0 1 1 0 */
#name span {
} /* 0 1 0 1 */
Slot 4
You’ll often see this fourth slot used for inline styles. That is only a score you write by hand. Inline style attributes are not part of specificity. They win because the cascade checks them in a step that comes before selector comparison.
Example:
<p style="color: red">Test</p> /* 1 0 0 0 */
An inline style beats any selector in a stylesheet or a style tag. !important beats both. I go into that order in the CSS cascade post.
Importance
Specificity does not matter if a rule ends with !important:
p {
font-size: 20px !important;
}
That rule will take precedence over any rule with more specificity.
!important is not a fifth slot. It is a separate cascade step. Another !important rule can still win if it has higher specificity, if it comes later, or through cascade layers. For !important, layer order is reversed: an earlier @layer wins.
:is(), :where(), :not() and :has()
:is(), :not() and :has() take the specificity of their most specific argument. :where() always has zero specificity, which is handy for low-specificity defaults.
:is(h1, #title) {
} /* 0 1 0 0, the #title argument */
:where(h1, #title) {
} /* 0 0 0 0 */
:is() and :where() work in Chrome 88, Firefox 78 and Safari 14. :has() is Chrome 105, Firefox 121 and Safari 15.4.
The universal selector * and combinators add nothing. Pseudo-elements count as a type.
Cascade layers (@layer, Chrome 99, Firefox 97, Safari 15.4) are checked before specificity. See the CSS cascade post, and the CSS selectors list if you want the selector types.
Tips
In general you should use the amount of specificity you need, but not more. In this way, you can craft other selectors to overwrite the rules set by preceding rules without going mad.
!important is a highly debated tool that CSS offers us. Many CSS experts advocate against using it. I find myself using it especially when trying out some style and a CSS rule has so much specificity that I need to use !important to make the browser apply my new CSS.
But generally, !important should have no place in your CSS files.
Using the id attribute to style CSS is also debated a lot, since it has a very high specificity. A good alternative is to use classes instead, which have less specificity, and so they are easier to work with, and they are more powerful (you can have multiple classes for an element, and a class can be reused multiple times).
Tools to calculate the specificity
You can use the site https://specificity.keegan.st/ to perform the specificity calculation for you automatically.
It’s useful especially if you are trying to figure things out, as it can be a nice feedback tool.
I also built a free CSS specificity checker: paste two or more selectors and it tells you which one wins and why.
Want me to talk about your product? You can sponsor this site.