How do CSS cascades work? The Dukh Chronicles
This article will be on the topic of CSS cascading. Welcome!
We need to know how the different CSS rules are applied to elements so that we can write clean, structural styles. We need to know how to actually add styles to elements with plain classes, or ids, or elements that might be children of other elements. This is usually straightforward, we just create a simple selector and the styling rules will do the work on their own. But, when we write styles for heavier applications, the code complexity will grow accordingly. In that case, we might come across situations in which some of our rules conflict with each other, and we need to predict how certain rulesets will behave, and how to write rules in order to create the best stylesheets possible.
Knowing cascades might come in handy in that case.
Creating some structure
We will learn cascades by building a simple card view consisting of my favorite pokemon:
To start, we will create a simple HTML structure and an accompanying stylesheet. We will do this in codepen, to reduce the configuration time of writing a full HTML document and chaining a stylesheet to id. (Yeah we will save 5 seconds tops 😀 ). Our code will look like in the following listing:
<div class="favorite-pokemon-container">
<h1 id="main-title" class="title">List of my favorite pokemon!</h1>
<ul id="main-list" class="list">
<li>
<a href="https://www.pokemon.com/us/pokedex/pikachu"
>Pikachu
<img
src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/025.png"
width="100"
/></a>
</li>
<li>
<a href="https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png"
>Charizard<img
src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png"
width="100"
/></a>
</li>
<li>
<a href="https://assets.pokemon.com/assets/cms2/img/pokedex/full/031.png"
>Nidoqueen<img
src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/031.png"
width="100"
/></a>
</li>
<li>
<a
class="rare"
href="https://assets.pokemon.com/assets/cms2/img/pokedex/full/144.png"
>Articuno<img
src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/144.png"
width="100"
/></a>
</li>
<li>
<a href="https://assets.pokemon.com/assets/cms2/img/pokedex/full/051.png"
>Dugtrio<img
src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/051.png"
width="100"
/></a>
</li>
<li>
<a href="https://assets.pokemon.com/assets/cms2/img/pokedex/full/094.png"
>Gengar<img
src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/094.png"
width="100"
/></a>
</li>
</ul>
</div>
Conflicts arise when two or more rules target the same element on our page. We will show how this is possible. We will target the heading, to change its style. If we look closely we will see that we can target the header in a couple of different ways, some listed below:
h1 {
color: red; /* tag */
}
#main-title {
color: blue; /* id */
}
.title {
color: black; /* class */
}
All three of these rules target the same element, and all of those rules are attempting to change the color of the text of the heading.
Which style does the browser apply? The browser follows a set of rules to determine which ruleset will apply. In our case, according to CSS rules, the second declaration, which uses the ID declarator will win, and the title will have the color blue. We call this set of rules a cascade. The cascade will determine how different conflicts are resolved, and it is one of the fundamentals of the CSS scripting language. The cascade is often misunderstood and many developers have issues with it (how many of us used and misused the operator just because?)
How cascade resolves a conflict?
When a conflicting declaration happens, the cascade takes a couple of things into account:
- The origin of the stylesheet, or where the styles come from. Styles that we write are applied in conjunction with the browser’s default styles
- Specificity of the selectors, which selectors take precedence
- Source order — order in which styles are declared in the stylesheets
With these rules, the browsers can behave accordingly when resolving any ambiguous declaration conflicts in the CSS.
Origin-based conflicts
These ones are not so well known. Basically, every browser has its own sets of default styles, which the browser applies when no other styles apply. These are called browser styles. They usually have lower priority than your stylesheets (called author styles), but they generally do basic styling, they style headings, paragraphs, lists and give them that basic browser look (top and bottom margins, left paddings, blue colors to links, etc.).
In our example, we can see some basic browser styles — our lists have a list-style-type of disc applied to it to produce those bullet points. Additionally, links are blue and underlined. Headings have top and bottom margins.
These can be simply overridden using our own styles. Considering that they don’t do anything unexpected and that they can be easily overridden, they are actually quite simple to resolve, by just setting your own values in the stylesheet. You can see a list of all the overridden browser styles by opening your developer tools and looking at the elements section.
The !important operator
Apart from browser and author rulesets, there is another operator that overrides every other in our stylesheet. If our styles have declarations that are marked as important, by adding !important
to the end of the declaration, then those declarations are treated as higher-priority rulesets. In the order of preference, they come before every other cascading style. It is an easy fix, and it solves the issue now, but we can run into a lot of trouble later down the road especially if we keep adding !important
to multiple declarations. I won't advise you against it, but its use should be controlled.
Specificity-based conflicts
If conflicting declarations can’t be resolved based on their origin, the browser tries to resolve them based on their specificity. Specificity is usually evaluated in two parts: styles applied inline in the HTML and styles applied using a selector.
Inline styles
If we apply styles using the HTML’s style attribute, the declarations are only applied to that element, and they override any other declaration which you might have. They have no selector because they are applied directly to the element. For example, in our pokemon list, we have one pokemon with the ‘rare’ link. We can apply inline styles to that specific element.
<li>
<a
class="rare"
style="background-color: purple;"
href="https://assets.pokemon.com/assets/cms2/img/pokedex/full/144.png"
>Articuno<img
src="https://assets.pokemon.com/assets/cms2/img/pokedex/full/144.png"
width="100"
/></a>
</li>
This sets the background-color of only this element to purple.
The only way inline declarations can be overridden is by using the !important
operator, to actually shift the style into a higher-priority origin. If the inline styles themselves are marked as !important
then there is actually nothing that can override them. Thus it is always better to change our styles in our stylesheets and not directly in our HTML code. We will revert this change and do some selector work.
Selector Styles
Now here is when it gets really interesting. When we actually talk about CSS cascades, we mostly only reference selector specificity. For example, a selector that has two classes has higher specificity than a selector with only one class.
If one rule sets the background color to orange, but another with a higher specificity sets the background color to green then the green styling is applied. What gives? If we try to override our ‘rare’ element with a class selector… it doesn’t work! Because the first selector is more specific than the second, it consists of an ID and a tag name, and the second only consists of a class name.
#main-list .rare {
background-color: salmon; /* this rule has an ID and it overrides the one below that consists of two classes */
}
.list .rate {
background-color: grey;
}
Why? Because an ID selector has a higher specificity than a class selector, and to be more precise a single ID selector has a higher specificity than a class selector, and a class selector has a higher specificity than a tag selector.
ID > Class > Tag
Basically, if a selector has an ID, it overwrites any of the other styles, if they have the same amount of IDs then the selector with the most classes wins, and otherwise, the selector with the most tags wins. For example, if we would like to select the title again, and to change its colors we would do something similar to the following listing.
html body div h1 {
color: red; /* fifth most specific rule, consists of tags */
}
body .favorite-pokemon-container h1 {
color: blue; /* fourth most specific rule, has a class */
}
.title {
color: pink; /* third most specific rule, only one class */
}
.favorite-pokemon-container .title {
color: skyblue; /* second most specific rule, having two classes */
}
#main-title {
color: orange; /* most specific rule, only one ID */
}
The most specific selector here would be the last one, which consists of only one ID, then the one that consists of two class names, then the one with one class name, then the first two, with the second having precedence over the first one due to the class used.
More often than not, declarations that have no effect on your styles happen due to more specific rules overriding it. Beginner developers often use IDs which by itself create a very high specificity, making it really hard later to override them. We can only override them by using a more specific ID. Just to recap what we’ve learned one more time — IDs are always more specific than anything else, more IDs are more specific than just one, classes are behind IDs when it comes to specificity, and tags are least specific.
Best practices for writing declarations when overriding other styles are: creating rulesets that have a higher specificity so that they override an existing ruleset (using more specific elements), or reducing the specificity of the ruleset we want to override so that it is easier to style it with new declarations.
Source-order conflicts
These are the third and final step to resolving the cascade. If the origin and the specificity are the same, then the declaration that appears later in the stylesheets takes precedence over a declaration that appears before it.
We can easily manipulate this by managing the stylesheet directly. If you create two selectors that are equal in specificity then whichever appears last wins.
a.rare {
color: pink;
}
.list a {
color: blue;
}
Pseudo styles
Pseudo-elements should also follow a certain structure because the source order of pseudo-elements affects the cascade. The correct order of pseudo-elements is, :link
, :visited
, :hover
and :active
. We need to follow these rules because later styles can override earlier styles. If the user hovers over a link, the hover takes precedence, but if the user clicks on the link then the active style takes precedence.
Just to recap some of the most important tips, don’t use IDs in your selector and don’t use !important
. IDs can mess up the specificity of the whole stylesheets, and there is almost no need to override the important operator.
Here we have our codepen:
Please check other blog posts on thedukh.com. For more CSS articles click on one of the links below:
Originally published at https://thedukh.com on April 8, 2021.