A primer on the cascade and specificity
A primer on the cascade and specificity ź“ė Ø
Almost as inevitable as death and taxes, developers will cite that the cascade and specificity are what makes CSS difficult to work with. Sure, the cascade and specificity are going to cause problems if you donāt account for them in your CSS, but if you have a base-level understanding, I promise, your CSS skills will sky-rocket.
A lot of the time, the cascade and specificity are explained within and inch of their lives in expansive guides or detailed articles. These articles are fantastic, but I think the size and complexity of them can scare developers too, so Iām going to simplify it for you today. In fact, keeping things simple is one of the most effective ways to keep any codebase manageable and maintainable. Itās especially the case with CSS though. Letās dig in.
The C in CSS stands for Cascade
Letās tackle the elephant in the room first. The Cascade is an algorithm that solves conflicts for you where multiple rules could apply to an HTML element.
Letās use a really simple example:
.my-element {
background: goldenrod;
background: coral;
}
This element has two background declarations. The cascade has done its thing and determined that .my-element will have a coral background. Why though?
Because the coral declaration comes after the goldenrod one, coral wins. In the vast majority of your standard CSS authoring, remember this and youāre good to go. There are exceptions though, so letās focus more on those because this first bit was dead easy right?
https://codepen.io/piccalilli/pen/KKYexpV/f9e635113d1dd8c40df86f1b990f045c Cascade And Specificity 1
The order of importance
No, this isnāt a weird British Royal Family thing; itās a simple ruleset to remember because not all properties are equal. What I mean by that is certain CSS property types will be prioritised over others. This list is ordered least to most specific.
Normal declarations
This is your background, padding, line-height and margin properties. Yāknow, the stuff youāre slinging into your code most of the day.
Active animations
This is when your animatable properties are being affected by an active @keyframes animation.
Declarations that utilise !important
Not to be feared but to be admired: the humble !important property does exactly what it says on the tin: it determines an important value. Itās added as part of your declaration value like so:
.my-element {
background: grey !important;
}
This will ultimately win (nearly) every time, but treat it as it will win every time and you wonāt have problems with it.
The !important property is designed to be honoured by proxy of it being important, so only use it when you feel you absolutely need to. Iāll write more on this if people want me to, but use it wisely and !important can be really useful and helpful in keeping your codebase simple.
The order of origin
This order is taken into account by the cascade from least specific to most specific.
User agent base styles
Every major browser ships with a user agent stylesheet. These are default styles that are applied to HTML elements to prevent every element looking the same if thereās no CSS that applies to that HTML.
Side Note
This is one of many reasons why writing semantic HTML is a good idea.
Local user styles
These styles are mostly operating system level stuff such as font size preferences. Local user styles also technically apply to CSS that is authored in browser extensions but my recommendation in more modern times is to presume that these are injected as <style> which will be more specific than styles authored in CSS files.
Authored CSS
Hey champ, this is your stuff that you are writing!
Authored !important
Yup, just like we covered earlier: those !important values are gonna win over your standard property values.
Local user styles with !important
Say you absolutely want your font size to be the same on every website. An operating system and browser combo could technically declare that as an !important property and that will beat all of the above.
User agent !important
Finally, if the user agent styles have an !important value, this will ultimately win.
Thatās it for the cascade, letās tackle its best friend: specificity.
Specificity
Fun fact, because Iām from Yorkshire in the UK, I struggle to say this word, which for someone who teaches CSS, is not ideal. I digress though, but for a reason ā to give you a quick breather ā so retract those shoulders and breathe out because specificity isnāt hard, trust me.
Letās go back to .my-element but tweak the CSS. Itās important to note that weāre using <div> element that has a class attribute.
div {
background: goldenrod;
}
.my-element {
background: coral;
}
The winner is coral because .my-element has a specificity score of 0-1-0, whereas div has a specificity score of 0-0-1. Letās dig into scoring some more.
https://codepen.io/piccalilli/pen/jORKeLZ/2556c5368a5e73ef0b66cfc31965d66d Cascade And Specificity 2
Specificity scoring
Each selector rule gets a score. The higher the score, the more likely that ruleās CSS is going to apply. The score is in this format: 0-0-0, or "hundreds"-"tens"-"singles". This does expand, but letās not worry about that right now until we need to. Letās do a quick fire scoring rundown.
FYI
Some people write specificity scores as comma seperated, like 0,0,0.
Universal selector ā AKA the wildcard selector
* {
/* Your CSS declarations and properties here */
}
Score: 0-0-0 ā 0 points
Type selector ā AKA element selector
h1 {
/* Your CSS declarations and properties here */
}
Score: 0-0-1 ā 1 point
Pseudo-element selector
::before {
/* Your CSS declarations and properties here */
}
Score: 0-0-1 ā 1 point
Class selector
.my-element {
/* Your CSS declarations and properties here */
}
Score: 0-1-0 ā 10 points
Pseudo-class selector
:hover {
/* Your CSS declarations and properties here */
}
Score: 0-1-0 ā 10 points
Attribute selector
[href] {
/* Your CSS declarations and properties here */
}
Score: 0-1-0 ā 10 points
ID selector
#myElement {
/* Your CSS declarations and properties here */
}
Score: 1-0-0 ā 100 points
Inline style attribute
<div style="background: blue"></div>
Score: 1-0-0-0 ā 1,000 points
!important rule
.my-element {
background: red !important;
}
Score: 1-0-0-0-0 ā 10,000 points
Wait just one minute, !important is a cascade thing, right?! Itās both a cascade and specificity thing. Someone with a name like Chad Smith might cite this as the reason why CSS āsucksā on Twitter. Thatās not true though because all you have to do is remember !important applies to the cascade and specificity, just like you need to remember that JavaScript canāt compare numbers properly (denysdovhan/wtfjs). All programming languages have stuff like this to learn and CSS is no different.
Also, do you remember when I said the scoring will expand? This is it. For both inline style attributes and !important rules we stitch on an extra number. For you, dear reader, this is a cue that the CSS youāre writing better be damn important because youāre in the realms of very high specificity.
Combining selectors equals more points
Now you know how many points each selector type gives you, letās combine some to show how this part works.
h1#myElement {
/* Your CSS declarations and properties here */
}
Weāve got 1 point because weāre using a type selector (h1) and 100 points because weāre also using an ID selector (#myElement). Therefore the specificity score here is 1-0-1 ā 101 points.
Letās do one more for fun.
h1.my-element::before {
/* Your CSS declarations and properties here */
}
Weāve got 2 points because weāre using both a type selector (h1) and a pseudo-element selector ::before. Weāve also got 10 points because weāre using a class selector (.my-element). Therefore the specificity score here is 0-1-2 ā 12 points.
:not(), :is() and :where()
Letās say you have this CSS:
h1:is(.my-element) {
/* Your CSS declarations and properties here */
}
It has the exact same specificity score as:
h1.my-element {
/* Your CSS declarations and properties here */
}
This is because with both :is() and :not(), the most specific selector passed into those pseudo-classes will be picked, so donāt think about using those as a specificity boost.
With :where(), that pseudo-class and any selector passed into it has no specificity. Letās go back to one of the earlier selectors to demonstrate.
h1#myElement {
/* Your CSS declarations and properties here */
}
This has a score of 1-0-1 ā 101 points. Letās add :where() into the mix.
h1:where(#myElement) {
/* Your CSS declarations and properties here */
}
This now has a score of 0-0-1 ā 1 point because the 100 points from the ID selector has now been discarded because it is within a :where() pseudo-class. Itās why you probably spotted its usage in CSS resets because those reset styles are much easier to override thanks to :where()ās low specificity.
Child and sibling selectors add no specificity
One quick thing to remember is using child and sibling selectors (>, ~ and +) wonāt affect your specificity score. For example, these have the same score as each other:
.my-element li {
/* Your CSS declarations and properties here */
}
.my-element > li {
/* Your CSS declarations and properties here */
}
Dev tools are your best friend
If you open up dev tools in your browser and go to the styles tab, itāll look a bit like this:

The stuff thatās less specific in terms of both the cascade and specificity will be lower down the panel. Anything more specific will be higher in the panel. What has been discarded will be crossed out, so by proxy, what isnāt crossed out is applied. That knowledge alone should make your life easier!
Wrapping up
I strongly recommend that you get a specificity calculator in your arsenal. My favourite is by the fantastic folks at Polypane. Their specificity calculator is super easy to use and does a really good job of breaking it down for you.
Thereās also new CSS capabilities that can help, such as Cascade Layers. I personally havenāt found the need for these (yet), so Iām not qualified to write about them. Iāll let someone who definitely is explain them to you instead.
Iāll finish by saying I find this stuff super boring to write about but itās really important to understand. I hope this primer will make you feel more comfortable with two of the most fundamental parts of CSS. If youāre looking for quick advice on how to deal with this in the real world, I would say keep your selectors as low scoring as possible. If you get stumped, remember the order of origin and use dev tools! Remember, if itās crossed out in the CSS panel: something more specific is in play.