Design

CSS Specificity: What You May Not Have Known

Most front-end developers glaze over at the mention of the word. However, having a strong grasp of how CSS Specificity works is key to minimizing tedious debugging. Specificity tells the browser what rules to apply to an element, and if there is a conflict then the higher priority wins.

Let’s look at some simple examples, and put this concept into context.

Pop Quiz – Question #1: Rule Order

<!-- HTML -->
<div class="blue red"></div>
/* CSS */
.red {
    background-color: red;
}
 
.blue {
    background-color: blue;
}

What is the background-color of the <div> based on the example above?

If you answered blue, you are correct.

Since the declaration comes later in the CSS (near the bottom), it has a higher specificity. This is fundamentally the simplest rule of the cascade in Cascading Style Sheets (CSS). If there is a conflict in rules (e.g. background-color: blue vs background-color: red), and since the classes have the same weight, then the cascade wins.

A similar scenario is the following:

<!-- HTML -->
<div class="box"></div>
/* CSS */
.box {
    background-color: red;
    background-color: blue;
}

Again, the same outcome, because background-color: blue comes later in the declaration block.

Ok, that was easy. Now let’s try one that’s a little harder:

Pop Quiz – Question #2: Pseudo-element vs Pseudo-class

<!-- HTML -->
<header>
    <h1>Pseudo-element vs Pseudo-class</h1>
</header>
/* CSS */
h1:first-child {
    color: blue;
}
 
h1::first-line {
    color: red;
}

What color will the text be?

This time red wins. The h1::first-line pseudo-element has higher specificity than the h1:first-child pseudo-class. Here is how we can calculate and know for sure:

Calculating Specificity

Chart demonstrating how to calculate CSS Specificity

Here are the rules for calculating specificity, as outlined by W3C:

  • Count the number of ID selectors in the selector (= a)
  • Count the number of class selectors, attributes selectors, and pseudo-classes in the selector (= b)
  • Count the number of type selectors and pseudo-elements in the selector (= c)
  • Count the number of element names and pseudo-elements in the selector (= d)
  • Concatenating the three numbers a-b-c (in a number system with a large base) gives the specificity.
/* Examples of calculating Specificity */
 
*             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
#x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

So let’s calculate and compare the Specificity of the example (Pop Quiz – Question #2: Pseudo-element vs Pseudo-class) up above to verify our answer was correct.

/* Pseudo-element vs Pseudo-class - calculating Specificity */

h1:first-child {} /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
h1::first-line {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */

A Few Special Rules

  • Ignore the universal selector (*), as it is worth 0.
  • !important overrides everything! Please avoid it at all costs.
  • :not() – a negation pseudo-class adds no specificity by itself. Only what’s inside the parenthesis is added to specificity value. e.g. #s12:not(FOO) /* a=1 b=0 c=1 -> specificity = 101 */

Conclusions

Basically, only be as specific as you need to be. Being too specific mostly makes it harder to override things further down the line.

Tools & Further Reading

Comments

2 thoughts on “CSS Specificity: What You May Not Have Known

  1. How this can be: “h1::first-line pseudo-element has higher specificity than the h1:first-child pseudo-class”? Specificity of h1:first-child is 22 and it’s higher than 2 of h1::first-line. So why blue doesn’t win? I don’t understand, is it some mistake?

    1. Technically speaking, `:first-line` will refer to a portion of the text node that is _the child_ of whatever element it is a part of. Whereas `:first-child` will apply to the element itself.

      In other words, if you were to apply styling to`h1:first-child` the text node is *inheriting* that style from its parent (the h1). `h1:first-line` will specifically refer to the first line of the text node and so is going to be the most specific selector for the text.

      Here’s a CodePen with a multi-line h1 that may help visualize what is happening a bit better: https://codepen.io/Cheffheid/pen/bGVdXNx

Have a comment?

Your email address will not be published. Required fields are marked *

accessibilityadminaggregationanchorarrow-rightattach-iconbackupsblogbookmarksbuddypresscachingcalendarcaret-downcartunifiedcouponcrediblecredit-cardcustommigrationdesigndevecomfriendsgallerygoodgroupsgrowthhostingideasinternationalizationiphoneloyaltymailmaphealthmessagingArtboard 1migrationsmultiple-sourcesmultisitenewsnotificationsperformancephonepluginprofilesresearcharrowscalablescrapingsecuresecureseosharearrowarrowsourcestreamsupporttwitchunifiedupdatesvaultwebsitewordpress