CSS Specificity
Ever get frustrated when a newly added CSS rule just doesn’t work? CSS specificity is how a browser determines which CSS properties will be applied to an element. If this is the first you’ve heard about this, don’t worry, a surprising number of engineers I meet don’t understand how CSS specificity works and can probably help explain why CSS naming conventions like BEM are so popular. (If you follow BEM, your score should typically never go past 0,1,0 or 0,2,0).
In a nutshell:
- The selector with the higher specificity score wins
- For selectors with the same specificity score, the last one wins (rule closest to the bottom of the stylesheet)
- Inline styles added to an element element overwrites any styles in the CSS
- !important will override all other specificity.
- If you have two matching !important rules, the last one wins.
How do you calculate specificity?
It’s actually pretty simple. CSS specificity is just counting. If you look at the specification:
A selector’s specificity is calculated as follows:
- 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)
- ignore the universal selector
Selectors inside the negation pseudo-class are counted like any other, but the negation itself does not count as a pseudo-class.
Concatenating the three numbers a-b-c (in a number system with a large base) gives the specificity.
* /* a=0 b=0 c=0 */
LI /* a=0 b=0 c=1 */
UL LI /* a=0 b=0 c=2 */
UL OL+LI /* a=0 b=0 c=3 */
H1 + *[REL=up] /* a=0 b=1 c=1 */
UL OL LI.red /* a=0 b=1 c=3 */
LI.red.level /* a=0 b=2 c=1 */
#x34y /* a=1 b=0 c=0 */
#s12:not(FOO) /* a=1 b=0 c=1 */
.foo :is(.bar, #baz) /* a=1 b=1 c=0 */
Lets break that down a little bit further.
Concatenating the three numbers a-b-c (in a number system with a large base) gives the specificity.
Here’s the tricky part. No matter how many type selectors you have, it’ll never trump a single class selector, and similarly no matter how many class selectors you have, it’ll never trump a single ID selector. Instead of using base-10, you can put a comma between each of the selector types.
So for example the top .foo selector cannot be overridden by a selector which only uses type selectors, no matter how many of them you add to your selector. For convenience, most folks will use base-10 since hopefully your CSS file doesn’t have selectors like this.
/* specificity=0,1,0 */
.foo
/* specificity=0,0,11 */
html body div ui li span span span span span span
What about inline styles?
Inline styles always overwrite any styles in the CSS. So the div in this case will be red, and this is true no matter how many id selectors we add to the css rule
<div id="green" style="background:red;"></div>
#green {background: green;}
!important
In this case, our div actually will be green, despite the inline selector, or any CSS rules. It’s common knowledge to avoid using !important when you can use alternative means, because you’ll likely waste a lot of time for your future-self or your co-workers when you can’t override that important rule you just added.
<div id="green" style="background:red;"></div>
#green {background: green !important;}
When using !important is OK
That being said, !important is a tool and there are times when it’s acceptable to use it.
- Overriding Media Query Styles – Need to override a rule for a particular target screen-size? You can avoid this in some cases, but if you use a CSS framework like Bootstrap, you already have these rules in place.
- Utility Classes – Want that .pull-right to really stick to the right? !important. It’s easily arguable that you don’t need utility classes at all, but this is how you get them to work.
- Email Styling – Email styling is like stepping back in time. You use tables everywhere, clients have terrible CSS support, and sometimes the only way to override default client styling is by using !important.