Lesson 1: Specificity
Specificity is the fundamental mechanism that CSS uses to decide which styles are more important than others.
If not managed properly, specificity can quickly spiral out of control and cause future updates to take twice as long.
In this lesson we’re going to cover:
- What specificity is and how it works.
- What bad specificity looks like it.
- How to fix bad specificity.
- How to avoid bad specificity.
How specificity works #
Let’s say you have two declarations which affect the color of a h1 element. Your browser needs some way of deciding which one to render and which one ignore.
Here the color: blue
style has won, but how did your browser decide to ignore color: red
? The element matches both declarations, so what’s going on?
Let’s talk specifics #
Specificity says that declarations which target specific elements should be chosen over declarations that generally style a wide scope of elements. It allows us to define a set of unspecific default rules that can be gradually overridden by more specific ones for our individual UI components.
Or put another way:
The wider the scope of elements matched by a declaration, the lower it’s specificity.
So in terms of CSS code, what does that mean? Going from least specific to highest, here are the different“tiers” of specificity:
- The Universal Selector (e.g.
*
) affects every element, therefore has no specificity. Styles defined with a*
selector can be overriden by any other declaration. - Type Selectors (e.g
h1
,div
) affect all elements of a given type, therefore have just a small amount of specificity. - Class Selectors (e.g.
.container
), Attribute Selectors (e.g.[type="radio"]
and Pseudo-Class Selectors (e.g.:hover
) have the same specificity. All are more specific than type selectors. - ID Selectors have even higher specificity. An ID is supposed to uniquely identify a single element on a page, so it makes sense that a style targeting that element’s ID is very specific.
- Inline styles (e.g.
style="color: red"
) will override any rules defined in external stylesheets. They’re even more specific than ID selectors. - Styles with an !important tag (e.g.
color: red !important
) will override any other declarations, including inline styles. The nuclear option.
There’s a few things to remember here, but if you focus on the principle of scope vs. specificity you’ll find it easier to recall what trumps what.
Example #
Let’s see an example of specificity in action:
It all seems to be working perfectly! There are a few more nuances that complicate things however…
Multi-part declarations #
Declarations that contain a mix of types, classes, IDs, etc will increase their specificity when compared against other declarations in the same specificity tier.
For example, #header
would be overridden by div#header
because it’s more specific. However, #header
wouldn’t be overriden by div.header
because both type and class selectors belong to lower specificity tiers than ID selectors.
A common way of calculating specificity is done by assigning scores to declarations:
*
has a score of 0,0,0,0div
has a score of 0,0,0,1.header
has a score of 0,0,1,0[type="radio"]
has a score of 0,0,1,0#header
has a score 0,1,0,0!important
has a score 1,0,0,0
In this setup, 1,0,0,0 > 0,1,0,0 > 0,0,1,0 > 0,0,0,1. When we mix and match, we increase the digit of the score that corresponds to each selector’s specificity tier.
div div div div div div div div div div
has a score of 0,0,0,10div.header
has a score 0,0,1,1div#header
has a score of 0,1,0,1
In this second setup, 0,1,0,1 > 0,0,1,1 > 0,0,0,10 — i.e. div#header
is the most specific. Despite there being ten div
tags in the first selector, div.header
would still be more specific because the class selector belongs to a higher specificity tier.
Understanding the above process is key to making specificity a friend instead of a foe. We’ll explore how a little later.
Specificity ties #
When two declarations have equal specificity and tie, the rule that is loaded last by the browser will be the one that gets chosen and rendered.
We’ll explore this aspect of specificity a bit more later, as it can become really useful when your codebase start to grow.
What is bad specificity? #
If you’ve worked in any reasonably-sized stylesheet codebase, you’ll know firsthand what bad specificity looks like. A strew of !important
tags, styles on arbitrary IDs and unneeded div
wrappers whose whole purpose is to nudge the specificity just a bit higher than other existing styles.
Future updates are made harder and harder with every increase in specificity.
A single ill-judged, high-specificity declaration that matches a wide scope of elements (e.g. div { background-color: red !important; }
😵) can force every subsequent update to dance around it. It can dominate a codebase and become difficult to remove. The only options are to carry on the dance, or to perform a potentially difficult extraction. The latter may involve unpicking every change that happened in the interim that relied on the bad declaration. Not an easy task whatsoever.
Graph it out #
To get insight on how well specificity is being managed in your codebase, plot a graph of your declarations’ specificity scores against their location in your stylesheets. There’s one or two good online sites out there that can do this for you.
If specificity has been managed well, the graph will be a smooth low line that slopes slightly upwards towards the end. In real terms, this means your lowest specificity declarations are being defined first and your highest last. Fewer declarations are being overwritten redundantly.
Good specificity graph #
If specificity hasn’t been managed well, the graph will be a spikey one that runs consistently high. This indicates that there’s lot of high-specificity rules. Declarations loaded after spikes will likely have to battle to override the“spikey” declarations, raising general specificity further.
Fixing bad specificity #
Fixing an existing codebase full of high-specificity declarations can be tricky. The idea is to generally lower specificity so that new styles aren’t required to be very specific to override existing ones.
Start with the spikey declarations that appearing earlier in your stylesheets. These are often the troublesome ones because styles that appear after them which target the same elements have to match their specificity.
If you’re dealing with high-specificity declarations which affect a lot of elements, you’ve got to be particularly careful. Before doing anything rash, try to list out everywhere that could be impacted by a change to one of these declarations.
Avoiding bad specificity #
Avoiding a codebase full of high-specificity declarations is much easier than fixing one. The general rule of thumb is to keep specificity as low as possible. A few tips to bear in mind when writing new styles:
Use class selectors for the majority of your styles #
There’s several reasons to prefer class selectors over other types of selectors:
- Class selectors have relatively low specificity, so are easily overridden.
- Classes are designed to be applied to multiple elements so are much reusable and portable, than say, ID selectors.
- If you give class names to everything you need to style, you’re decoupling your styles from the structure of your markup. The type and document position of an element can change freely without wrecking your styles. Getting into this habit requires a little more typing, but has the added benefit of annotating and documenting your markup.
Avoid styling using ID selectors #
Declarations that use IDs selectors have very high specificity. It’s very unlikely that you need to use them. Prefer styling with class selectors instead.
Avoid !important tags #
When there’s !important
tags everywhere, it’s a sign that specificity has gotten out of control. For the vast majority of situations, you shouldn’t need them.
The exception to this is when you need to override high-specificity styles generated by third party libraries. If for some reason, a library adds inline styles to an element you want to style, you’re going to have to resort to an !important
to override it.
Avoid multi-part declarations for scoping styles #
Every tag you add a selector to, the higher it’s specificity becomes.
If you write declarations that target descendants (e.g. .header .title
) you’ll increase it’s specificity for every descendant you specify. But we regularly want to write styles that are“scoped” to a specific container.
Instead we could incorporate the container’s class name into the element’s class name. E.g. instead of .header .title
, we could change the element’s class name to .header-title
and style on that instead. Using this method, we scope the style by“namespacing” the class name with a prefix and avoid increasing specificity.
Avoid multi-part declarations for variations #
Sometimes we need to change an element’s styles depending on what state it’s in. For example, we may have some .button
elements, of which some may be disabled. A common approach would be to add a .disabled
class to the elements and style them using a .button.disabled
selector. Like with the previous section, doing so would increase specificity.
Instead we could employ a similar strategy and prefix the“variation” class with the container’s class name. Instead of .button.disabled
, we’d have .button-disabled
. That way the variation is again scoped to the container, and also has low specificity.
Use load order to control overrides #
Let’s return to the subject of specificity ties. When two or more declarations with equal specificity match the same element, the one that is loaded last wins. We can use this to our advantage.
If we manage to create a stylesheet mainly comprised of class selector declarations, all of which share the same specificity, we can control which declarations are most important simply by changing the order in which they appear in the stylesheet. We essentially side step specificity and are able to employ a more intuitive and manageable override system.
As our variation classes should always override default styles, we can place them last in our stylesheet. Providing no higher-specificity declarations have snuck in, they should always override the default declarations.
End of Lesson 1 #
How specificity works should hopefully now make a little bit more sense. It’s big part of CSS, but also one that most commonly causes problems. When writing new styles, always be thinking about keeping specificity as low as possible, otherwise you may end up in a bit of a mess.
Useful resources #
Lessons
- Lesson 1: Specificity ← You're here
- Lesson 2: Scope
- Lesson 3: Naming
- Lesson 4: Variations
- Lesson: BEM Coming Soon
- Lesson: SCSS Coming Soon
-
Lesson:
position
Deep Dive Coming Soon - Lesson: Reusability Coming Soon
- Lesson: Units Coming Soon
- Lesson: Media Queries Coming Soon
-
Lesson:
display
Deep Dive Coming Soon - Lesson: Flexbox Coming Soon
- Lesson: Grid Coming Soon
- Lesson: Inheritance Coming Soon
- Lesson: Layout vs Aesthetics Coming Soon
- Lesson: Fixing Bad CSS Coming Soon
- Lesson: Composability Coming Soon
- Lesson: File Organisation Coming Soon
- Lesson: Accessibility Coming Soon