What is Specificity in CSS?

CSS is read from top to bottom, this is important because it means that we need to be mindful about the order in which we select our elements, especially when we're selecting the same element twice.

So for example, just to demonstrate, in my index.html, we see, in my body element, I just have this h1 element.. and then in my CSS, we see, I'm already selecting it, and giving it a color of red, and we see it's red in our live server.

<!-- index.html -->
<body>
<h1>Hello World</h1>
</body>
<!-- index.html -->
<body>
<h1>Hello World</h1>
</body>
/* style.css */
h1 {
color: red;
}
/* style.css */
h1 {
color: red;
}

But now, underneath our h1 ruleset, what would happen if I reselected our h1 element, and also gave it a color property, but this time, another color, like green. When I save, we see, our h1 is now green.

/* style.css */
h1 {
color: red;
}

h1 {
color: green;
}
/* style.css */
h1 {
color: red;
}

h1 {
color: green;
}

When two rulesets have the same selector, and both are attempting to define the same property but with different values, what you get as a result, is conflicting rulesets.

Our two rulesets are conflicting with each other, they're both competing to apply their styles on the same element, and on the same property.

In this case, the conflict is resolved by the order of appearance. Since CSS is read from top to bottom, our second ruleset gets interpreted last, and therefore our h1 receives the color of green.

The Specificity Algorithm

Now unfortunately it's not always so simple.. For example, in my index.html, I'll give my h1 element a class called title, and then back in my CSS, on my first ruleset, instead of selecting all h1 elements by their element name, I'll select our h1 element by the class we've just given it, and when I save, we see, despite the order of appearance, our first ruleset is the one getting applied, and we see our heading is red instead of green.

<!-- index.html -->
<body>
<h1 class="title">Hello World</h1>
</body>
<!-- index.html -->
<body>
<h1 class="title">Hello World</h1>
</body>
/* style.css */
.title {
color: red;
}

h1 {
color: green;
}
/* style.css */
.title {
color: red;
}

h1 {
color: green;
}

The reason this is happening, is because it turns out CSS uses an algorithm to resolve conflicting rulesets, a point system called specificity.

This algorithm, specificity, what with it being a point system, will resolve conflicting rulesets by looking for the selector with the most points, almost like how games have a scoring system, and the player with the highest score wins the game.

At the very top of my CSS file, I'll add a comment that says, style attribute, ids, classes, and elements.

/* style.css */
/* style attribute, ids, classes, elements */
/* style.css */
/* style attribute, ids, classes, elements */

Now above both of my existing rulesets, I'll add a comment above both of them, that says 0 0 0 0.

/* style.css */
/* style attribute, ids, classes, elements */
/* 0 0 0 0 */
.title {
color: red;
}

/* 0 0 0 0 */
h1 {
color: green;
}
/* style.css */
/* style attribute, ids, classes, elements */
/* 0 0 0 0 */
.title {
color: red;
}

/* 0 0 0 0 */
h1 {
color: green;
}

These zeros above my rulesets represent the point system CSS uses under the hood, to determine the specificity of a selector.

The first 0 represents style attributes, the second represents ID selectors, the third represents class selectors, and the fourth represents selecting elements by their element name.

If I was to distribute points across our two rulesets, on the first, since we're using a class selector, I can replace the second to last zero with a one, and then on my second ruleset, since we're selecting our h1 by it's element name, I can replace the last zero with a 1.

/* style.css */
/* style attribute, ids, classes, elements */
/* 0 0 1 0 */
.title {
color: red;
}

/* 0 0 0 1 */
h1 {
color: green;
}
/* style.css */
/* style attribute, ids, classes, elements */
/* 0 0 1 0 */
.title {
color: red;
}

/* 0 0 0 1 */
h1 {
color: green;
}

Points that are further left are worth more points than those to the right. Style attributes are worth the most, ids are worth the second most, classes are worth the third most, and selecting elements by their element name is worth the least.

Our first ruleset uses a selector worth more points than the one used on our second ruleset, therefore despite the order of appearance, the first ruleset wins the conflict.

ID Selectors

Now I'll head over to my index.html, and I'll give my h1 element an id called title.. Then in my CSS, above our first ruleset, I'll reselect our h1 element, but this time by it's id, and I'll give it a color of purple. By adding this new ruleset, we've just added another conflicting ruleset in the mix, but before I save, what do you suppose is going to be the color of our heading when I run this code? When I save, we see, our heading is now purple.

<!-- index.html -->
<body>
<h1 class="title" id="title">Hello World</h1>
</body>
<!-- index.html -->
<body>
<h1 class="title" id="title">Hello World</h1>
</body>
/* style.css */
/* style attribute, ids, classes, elements */
/* 0 1 0 0 */
#title {
color: purple;
}

/* 0 0 1 0 */
.title {
color: red;
}

/* 0 0 0 1 */
h1 {
color: green;
}
/* style.css */
/* style attribute, ids, classes, elements */
/* 0 1 0 0 */
#title {
color: purple;
}

/* 0 0 1 0 */
.title {
color: red;
}

/* 0 0 0 1 */
h1 {
color: green;
}

Above my new ruleset, I'll add a comment that says 0 0 0 0, and since this ruleset is using an id selector, I'll replace the second zero for a 1. This new ruleset uses a selector worth more points than any of the other ones, and so despite the order of appearance, this ruleset wins the conflict.

Style Attributes

Finally, to demonstrate the highest specificity, in my index.html, I'll give my h1 element the style attribute, and I'll give it a color of pink. I'll also add a comment above my h1 element that says 0 0 0 0, and since we're using the style attribute, I'll replace the first 0 with a 1. When I save, we see, our h1 is now pink, and this is because the style attribute is worth more points that everything else.

<!-- index.html -->
<body>
<!-- 1 0 0 0 -->
<h1 class="title" id="title" style="color: pink;">Hello World</h1>
</body>
<!-- index.html -->
<body>
<!-- 1 0 0 0 -->
<h1 class="title" id="title" style="color: pink;">Hello World</h1>
</body>
/* style.css */
/* style attribute, ids, classes, elements */
/* 0 1 0 0 */
#title {
color: purple;
}

/* 0 0 1 0 */
.title {
color: red;
}

/* 0 0 0 1 */
h1 {
color: green;
}
/* style.css */
/* style attribute, ids, classes, elements */
/* 0 1 0 0 */
#title {
color: purple;
}

/* 0 0 1 0 */
.title {
color: red;
}

/* 0 0 0 1 */
h1 {
color: green;
}

The style attribute is worth the most amount points, and therefore it has the highest specificity.. However, as a last resort, there is still one way to beat it. Back in my CSS, on my last ruleset, which by the way has the least amount of points, against my color of green, I’ll say !important. This important keyword automatically wins over any conflict, and when I save, we see, despite this ruleset having the lowest specificity, our heading is green.

/* style.css */
/* style attribute, ids, classes, elements */
/* 0 1 0 0 */
#title {
color: purple;
}

/* 0 0 1 0 */
.title {
color: red;
}

/* 0 0 0 1 */
h1 {
color: green !important;
}
/* style.css */
/* style attribute, ids, classes, elements */
/* 0 1 0 0 */
#title {
color: purple;
}

/* 0 0 1 0 */
.title {
color: red;
}

/* 0 0 0 1 */
h1 {
color: green !important;
}

Specificity Brackets

Now there's one last thing I want to show you. In my index.html, I’ll add an unordered list with one list element inside it. Inside this list element I’ll add a ordered list and one list element inside it. Inside this list element, I’ll add an unordered list with one list element inside it. Inside this list element, I’ll add a ordered list and one list element inside it. And finally, inside this list element, I’ll add an unordered list with one list element inside it. Now I'm just going to move my h1 element inside our deeply nested list element.

<!-- index.html -->
<body>
<!-- 1 0 0 0 -->
<ul style="color: pink;">
<li>
<ol>
<li>
<ul>
<li>
<ol>
<li>
<ul>
<li>
<h1 class="title" id="title">Hello World</h1>
</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ol>
</li>
</ul>
</body>
<!-- index.html -->
<body>
<!-- 1 0 0 0 -->
<ul style="color: pink;">
<li>
<ol>
<li>
<ul>
<li>
<ol>
<li>
<ul>
<li>
<h1 class="title" id="title">Hello World</h1>
</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ol>
</li>
</ul>
</body>

Back in my CSS, I’ll remove the !important keyword from the color of green, and when I save, our heading is back to being pink since in my HTML, our h1 element is getting a color of pink from it's style attribute. But back in my CSS, on my last ruleset, instead of just selecting my h1 element like this, I’m going to do something silly and select my ul, my li, my ol, my li, ul, li, ol, li, ul, li and finally my h1.

/* style.css */
/* style attribute, ids, classes, elements */
/* 0 1 0 0 */
#title {
color: purple;
}

/* 0 0 1 0 */
.title {
color: red;
}

/* 0 0 0 11 */
ul li ol li ul li ol li ul li h1 {
color: green;
}
/* style.css */
/* style attribute, ids, classes, elements */
/* 0 1 0 0 */
#title {
color: purple;
}

/* 0 0 1 0 */
.title {
color: red;
}

/* 0 0 0 11 */
ul li ol li ul li ol li ul li h1 {
color: green;
}

We’re now selecting eleven elements with the descendant combinator selector, which is kind of insane.. But since we're selecting eleven elements by their element name, I’ll update the point system above our ruleset, from 1 to 11.

This selector is worth 11 points on the fourth bracket.. This is a lot, but despite it being a lot, when I save, we see, nothing happened, our heading is still pink. This is because brackets only compete with their own brackets. This means that, it doesn’t matter how many points we have in our last bracket, the style attribute bracket is still the winner.

If I head over to my index.html, and remove the style attribute from our h1 element, and save, we see, our highest winning bracket is now the ruleset in my CSS with the id selector, and so our heading is purple.

If I comment out this ruleset, it's now our ruleset with the class selector that has the highest winning bracket, and our heading is now red.

And finally if I comment it out, of course now our h1 has a color of green.

However, because brackets only compete with their own brackets, this 11 point element selector will win against other rulesets that uses element names for selectors.

So for example if I add new ruleset underneath this 11 point one, and select my h1 by it’s element name, and give it a color of brown, we see, when I save, our h1 is still green. I'll add a comment above it that says 0 0 0 0 and since we're selecting our h1 element by it's element name, I'll replace the last 0 for a 1.

/* style.css */
/* style attribute, ids, classes, elements */
/* 0 0 0 11 */
ul li ol li ul li ol li ul li h1 {
color: green;
}

/* 0 0 0 1 */
h1 {
color: brown;
}
/* style.css */
/* style attribute, ids, classes, elements */
/* 0 0 0 11 */
ul li ol li ul li ol li ul li h1 {
color: green;
}

/* 0 0 0 1 */
h1 {
color: brown;
}

Both of these rulesets are competing within their own bracket, and despite the order of appearance, the one with the highest points wins the conflict.

For the order of appearance to win a conflict, competing rulesets must be competing within the same bracket and have the same amount of points. For example, if I copy my eleven selectors, and paste them on my new ruleset, and save, we see, our heading is now brown.

/* style.css */
/* style attribute, ids, classes, elements */
/* 0 0 0 11 */
ul li ol li ul li ol li ul li h1 {
color: green;
}

/* 0 0 0 11 */
ul li ol li ul li ol li ul li h1 {
color: brown;
}
/* style.css */
/* style attribute, ids, classes, elements */
/* 0 0 0 11 */
ul li ol li ul li ol li ul li h1 {
color: green;
}

/* 0 0 0 11 */
ul li ol li ul li ol li ul li h1 {
color: brown;
}

I'll update the points from 1 to 11, and now that both rulesets share the same bracket, and have the same amount of points, the order of appearance is now the determining factor in resolving the conflict.

Now as you can see, specificity is obviously very complicated, and is probably the culprit for a lot of the headaches people get when working with CSS.

As a best practice, and especially to mitigate how unruly specificity becomes as your app grows in scale, a common convention, is to simply default to using classes for selecting everything. You can literally avoid all of the headaches, issues, complications, and unpredictability, by just always selecting your elements with classes.

When everything you want to style is selected with classes, you create consistency in your specificity, to where you no longer even need to think about it, and only ever have to think about the order of appearance, which is inherently much easier to track. By simply defaulting to using classes, you make CSS substantially easier to work it.