Introduction
Let’s take this piece of code:
<style>
.red {
color: red;
}
.blue {
color: blue;
}
</style>
<div class="red blue">Hello world</div>
What is the color of the text? Let’s try it out:
Congratulations if you picked blue!
The reason why the color appears blue is because the selector was defined last.
Now let’s try the same thing with:
<style>
#id {
color: green;
}
.red {
color: red;
}
div {
color: blue;
}
</style>
<div id="id" class="blue">Hello world</div>
What is the color of the text now? Let’s try it out:
It appears green. But why so?
Specificity
General rule
CSS uses multiple rules to determine how to apply styles and selectors to elements. One of them is the specificity: this is a score computed for each selector. Once all of them are computed, CSS will pick the selector with the highest specificity.
Let’s circle back to our 2nd example. If we display the specificity of each selectors, we have:
#id { color: green; } /* Specificity: (1, 0, 0) → WINS */
.red { color: red; } /* Specificity: (0, 1, 0) */
div { color: blue; } /* Specificity: (0, 0, 1) */
Which explains why the text appears green
: #id
has the highest specificity.
Egality
If 2 selectors have the same specificity, then the last defined will take over, which explains the situation in our 1st example:
.red { color: red; } /* Specificity: (0, 1, 0) */
.blue { color: blue; } /* Specificity: (0, 1, 0) & last one → WINS */
How to compute the specificity
General rules
The specificity is a list of 3 elements: (A, B, C)
, where:
-
A
is the ID selectors (e.g.,#example
) -
B
is the class selectors (e.g.,.example
), attributes selectors (e.g.,[type="radio"]
) and pseudo-classes (e.g.,:hover
) -
C
is the type selectors (e.g.,h1
) and pseudo-elements (e.g.,::before
)
If you want to check the differences between pseudo-classes (like
:hover
) and pseudo-elements (like::before
), you can check this post: https://dev.to/ayc0/css-before-vs-before-1d35.
Edge cases
-
*
(universal selector) and:where()
have no specificity(0, 0, 0)
. -
:is(…)
,:not(…)
, and:has(…)
pseudo-classes have the specificity of their most specific complex selector in their selector list argument. For instance::not(.class)
will have the same specificity as.class
(aka(0, 1, 0)
) -
:nth-child(…)
and:nth-last-child(…)
have the specificity of their most specific complex selector in their selector list argument plus their own specificity For instance::nth-child(2n of .class)
will have the same specificity as.class
+:nth-child
(aka(0, 1, 0) + (0, 1, 0) = (0, 2, 0)
).
Examples
-
.a.b.c
->(0, 3, 0)
(3 classes) -
div#id
->(1, 0, 1)
(1 id + 1 element) -
html
->(0, 0, 1)
|:root
->(0, 1, 0)
(which in https://dev.to/ayc0/lightdark-mode-corrections-5e19#-raw-root-endraw-with-class-names I said that they both represent the same element with just different specificity -
span > a[href$=".org"]:not([disabled])
->(0, 2, 2)
(2 elements (span & a) & 2 attributes selectors (href & not disabled)
Combining selectors
When writing CSS rules, multiple selectors can be passed:
[type="password"],
input:focus,
:root #myApp input:required {
color: blue;
}
In those cases, it won’t create 1 giant agglomerated selector. Instead CSS will treat those as 3 distinct rules:
[type="password"] {
color: blue;
}
input:focus {
color: blue;
}
:root #myApp input:required {
color: blue;
}
And then it’ll continue as normal: find the selector with the highest specificity and apply it.
If you want to know how to bump or increase the specificity of a selector, I’ll shortly release a separate article about it.
Inline style
There are 2 ways of injecting styles in HTML:
- CSS style sheet that use selectors with specificity,
- inline style in HTML.
Inline style doesn't use selectors, and thus cannot have specificity. So how do they fit in this system?
We can think about it as a 4th element in the list I
: (I, A, B, C)
, that has more importance than the 3 others we’ve seen previously.
With this logic, in this example, "Hello World" should appear purple:
<style>
#id {
color: green;
}
.red {
color: red;
}
div {
color: blue;
}
</style>
<div id="id" class="blue" style="color: RebeccaPurple">
Hello world
</div>
!important
Even if !important
has nothing to do with specificity, it allows styles to be applied no matter what their specificity.
Which means that in this example: even if div
has the lowest specificity, its style will be applied and the text will appear blue:
<style>
#id {
color: green;
}
.red {
color: red;
}
div {
color: blue !important;
}
</style>
<div id="id" class="blue" style="color: RebeccaPurple">
Hello world
</div>
Another article dedicated to how !important
works will be released in the future.
Tools
Parsel
When selectors become too complex, you can use Parsel, a library built by Lea Verou:
.bar + div.k1.k2 [id='baz']:nth-child(2n of .k3):not(:where(#yolo))::before" and determining its specificity (1,6,2)"/>
VSCode
Similarly, VSCode can parse selectors and display their specificity on hover.
.bar + div.k1.k2 [id='baz']:nth-child(2n):not(:where(#yolo))::before""/>
Top comments (0)