DEV Community

Cover image for How to calculate CSS specificity of your style rules
Smitter
Smitter

Posted on • Edited on

How to calculate CSS specificity of your style rules

In this article, you will learn how to calculate CSS specificity of the styles you write by computing a compound number to measure specificity weight by CSS selector.

First off,

Specificity is the algorithm browsers use to establish the CSS declarations that will get applied to an element, when it is referenced by two or more style rules.

Specificity algorithm calculates weight of CSS selector, and among competing selectors targetting one element, the selector with the greatest weight will win to apply CSS declarations to that element.

Visual definition of CSS terms

CSS syntax definitions

The 3-column value

The specificity algorithm bases weight calculation around a three-column compound number. Each column represents weight that correspond to the three types of CSS selectors, i.e ID, CLASS and TYPE. This three-column compound number starts off with zeroes looking like:

Specificity 3-column value initial

  • ID column - Includes only ID selectors, such as #app. For each ID in a matching selector, add [1, 0, 0] to the weight value.
  • CLASS column - Includes class selectors, such as .myInput, attribute selectors e.g [type="password"], and pseudo-classes such as :hover, :first-of-type, e.t.c. For each class, attribute selector, or pseudo-class in a matching selector, add [0, 1, 0] to the weight value.
  • TYPE column - Includes type selectors, such as p, h2, and a, and pseudo-elements like ::before, ::placeholder, and all other selectors with double-colon(::) notation. For each type or pseudo-element in a matching selector, add [0, 0, 1] to the weight value.

Specificity comparison

Let's look at the following rule, which is composed of 7 selectors to target a span element:

#app #navigation #list .sub-list .item p span {
    /* declarations here */
}
Enter fullscreen mode Exit fullscreen mode

There are 3 ID selectors(#app, #navigation, #list). So the three-column value becomes [3, 0, 0].

Then 2 class selectors(.sub-list, .item). The three-column value compounds to [3, 2, 0].

Then 2 type selectors(p, span). The three-column value compounds to [3, 2, 2].

Now provided the number in each column is 9 or less, we can concatenate the numbers in each column to get a base10 number as the resultant specificity weight. So in this case we have a calculated specificity weight as 322.

Disclaimer: This is not how specificity is calculated by CSS processor inside browsers, but only a blue print. Normally in browsers, the specificity is not calculated in base 10 number system, but in a larger number, often unspecified.

Suppose we have another rule styling the same span element looking like this:

#navigation #list .sub-list .item .wrapper p span {
    /* declarations here */
}
Enter fullscreen mode Exit fullscreen mode

Likewise in the style rule above, 7 selectors are used to target the span element. Difference being that we have 2 ID selectors and 3 class selectors, which results in the three-column value [2, 3, 2]. Hence a specificity weight 232. Because 322 is greater than 232, the former has precedence over the latter. Therefore, the former style rule will be processed to style the span element.

Selectors that add no weight to specificity calculation

The universal selector * and the pseudo-class :where() with its parameters do not add onto specificity weight so their weight is [0, 0, 0]; but they do match elements.

Combinators, such as +,>, ~, " ", and ||, may make a selector more specific in what is selected but they don't add any weight to specificity.

:not(), :has() and :is() pseudo classes add no weight by themselves. However their parameters do impact weight in specificity calculation. Therefore, the weight calculated from the selectors, is inclusive of the weight from the parameters of these pseudo-classes.

Incase of a parameter list, weight by the parameters with the highest specificity is used. For example:

/* 
<span id="intro" class="lead">...</span> 
*/

span:is(.lead, #intro) {
    /* calculated specificity - [1, 0, 1] */
}
Enter fullscreen mode Exit fullscreen mode

In the above snippet, inside the :is() parameter list, #intro has higher weight ([1, 0, 0]) than .lead ([0, 1, 0]) . Hence weight by #intro is used to sum up with weight by the span selector resulting to [1, 0, 1].

Example

Consider the following HTML:

<html>
    <head>
        ...
    </head>
    <body>
        <main id="myElement">
            <input type="password" required class="myInput" />
        </main>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

In the below CSS, three rules are targeting <input /> element to set a color. For a given input, color value is applied should its selectors specificity weight have precedence over the other matching selectors.

/* FIRST RULE */
#myElement input.myInput {
    color: red;
} /* [1, 1, 1] */

/* SECOND RULE */
input[type="password"]:required {
    color: blue;
} /* [0, 2, 1] */

/* THIRD RULE */
html body main input {
    color: green;
} /* [0, 0, 4] */
Enter fullscreen mode Exit fullscreen mode

All the above rules have selectors matching the same and only <input /> element on our HTML. The input will have a color red because the first rule has the highest specificity weight, i.e 111 is the greatest number from the weights of 111, 021 and 004.

The last rule has 4 type selctors(html, body, main, input). Increasing even more type selctors will still result in <input /> being color red since specificity weight by type selector is always lower than ID selector.

Excercise

Consider the same html to deduce the result of the below CSS:

.myInput {
    border: 0;
}

:is(input.myInput) {
    border: 1px solid black;
}

:where(#myElement input[type="password"]) {
    border: 4px solid blue;
}
Enter fullscreen mode Exit fullscreen mode

What will happen to border on the <input /> element?

Share your answer in the comments👍️.

A Column number that is greater than 9

I had mentioned earlier that to get the specificity weight you concatenate the column values to get a number like 322. This is because for less than 10 selectors of a type(either ID, Class or Type), we can calculate the specificity in the base 10. Hence concatenating, we can get a number in the base10 number system.

This is not the case when we have 10 and above selectors of a type. High number of selectors can occur when using nesting feature of css preprocessors like sass to generate css. You can end up with three-column value like [11, 7, 19]. You can't concatenate the numbers to obtain a decimal specificity weight in this case.

But instead you can convert the column numbers to a higher base.

For example, lets say we have a three-column value of [11, 7, 19]. To calculate the weight, we can pick a base number that is larger than the largest column number in the three-column value.

Let's pick a base number 20 which is bigger than 19(largest number in [11, 7, 19]). Then multiply out the three columns starting rightmost working to the left. And add the results, like shown:

(20) * 19       =     380
(20*20) * 7     =   2 800
(20*20*20) * 11 =  88 000
-------------------------
Total in decimal = 91 180

Enter fullscreen mode Exit fullscreen mode

Doing this will also require you to use that base number to calculate specificity weight for the other competing selectors. Thankfully, CSS processor handles all this for you. so you just have to understand "why a certain style has been applied and not the other".

Equivalent weights

Where 2 or more style rules have exactly the same specificity weight, the most recent rule will take precedence. However you can force a rule to a higher precedence over other equivalent rules using !important keyword like:

p {
    color: #00ff00 !important;
}
Enter fullscreen mode Exit fullscreen mode

When you do this, all previous style rules with equivalent weight are overridden(including ones using !important) and any equivalent rules that are processed will be ignored.

For example:

p {
    color: #00ff00 !important;
} /* [0, 0, 1] */
p {
    color: #ff0000;
} /* [0, 0, 1] */
Enter fullscreen mode Exit fullscreen mode

Both rules have the same weight but the first rule will take precedence because of use of !important keyword. Otherwise, the second rule would have taken precendence.

Conclusion

CSS has a simple syntax with alot happening behind scenes with the CSS processor. We have talked about specificity in this article, but specificity only comes to play after browser has determined cascade origin and importance. You can explore more which is beyond scope of this article.

In a nutshell, knowing specificity of your styles helps you construct rules and understand what precedence they will have.

We have not talked about inline styles, which are the most specific, meaning that when an inline style is applied, it has the highest specificty overriding any style in the style sheet. The only way to override an inline style from a style sheet is to use !important keyword.


Meet me aside on

I periodically share useful content you may not want to miss.

Top comments (4)

Collapse
 
tracygjg profile image
Tracy Gilmore • Edited

Hi Smitter,
A very well prepared and informative article. Thank you.
I think CSS specificity is actually calculated from 5 going on 6 factors. I remember them using the mnumonic IS-ICE

  • I: Important overrides specificity in most cases.
  • S: Style has an overriding effect.
  • I: ID attribute/selector
  • C: Class attribute/selector
  • E: Element selector

I have a demonstration on CodePen.

The 6th factor, which I have yet to utilise myself is @layer, which have a scoping effect.

Regards, Tracy

Collapse
 
smitterhane profile image
Smitter

Yes the factors you have mentioned are totally right. In this article I was abit more focused on how to calculate the weight as a number that causes a style to be applied instead of the other. Acording to the mnemonic IS-ICE, i may say the article has explained in detail on the ICE part. Towards the end, I have touched lightly on IS part:

For example:

p {
   color: #00ff00 !important;
} /* [0, 0, 1] */
p {
   color: #ff0000;
} /* [0, 0, 1] */

Both rules have the same weight but the first rule will take precedence because of use of !important keyword. Otherwise, the second rule would have taken precendence.

Where I mean that the most recently applied style wins, BUT if only the previous style does not have an !important keyword.

Regarding the @layer, When you don't specify a layer in your stylesheet, the CSS processor will place your styles(those not in a layer) into an anonymous layer. Now styles in anonymous layer have a higher specificity weight than a custom layer you define on your stylesheet. A good usecase can be to put bootstrap styles in a layer you have created.
And your own styles you write not enclosed in a layer, or in a layer you have created more recently. This causes CSS processor to always apply your styles overriding what bootstrap has set.

Thanks for the mnemonic, It makes it easy to remember

Collapse
 
fruntend profile image
fruntend

Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍

Collapse
 
smitterhane profile image
Smitter

thank you