How CSS Resolves Style Conflicts
Style conflicts happen
Let's say you and the other CSS developer on your team are creating button styles.
Your design team passed you the final sketch/figma files and you find out they have a thing for smooth pill-shaped buttons.
So you and your CSS pal code the following to match those button styles.
<p> CODE: https://gist.github.com/jginbound/1b1135dc7d29ce04789c670b8f56ac74.js</p>
However, the months go by and your team's stylesheet gets bloated with superfluous code. Somewhere in the stylesheet, a small, innocent change in the CSS causes a massive style override, and all of the buttons now have a blue text color:
How did this happen? It turns out that after investigating the git logs, a newly hired dev on the team accidentally overrode the button styles in order to make all anchor links of a blue color.
Take a look at line 18 in the stylesheet below. The !important tag was used to "brute-force" the color on all anchor-links from white to rgba(0, 50, 200, 0.8).
<p> CODE: https://gist.github.com/jginbound/5e34ee4320f48ef898e5f46b3ada5436.js</p>
Because !important tags can override any style declaration, they are considered a bad practice in writing CSS code.
Now take a look at this simple example of a style-conflict:
At first, the paragraphs is styled with a red color and font-size of 1rem. But right below it, the paragraphs are later styled with a new color of purple and font-size of 2rem.
Which style wins in the end? How does CSS go about enforcing which styles get applied eventually?
CSS uses what's known as the "Cascade", which is an algorithm for figuring out how styles will be applied to a webpage once it has loaded in the browser. While figuring out how styles will be applied to HTML elements, the cascade algorithm also resolves any style conflicts along the way.
First, let's get some terminology straight
CSS is a language whose purpose is to style HTML elements that's already stored in memory and displayed in the browser.
1. This is an html element
2. A CSS Selector is how CSS identifies and targets an HTML element which will then receive style declarations
Once CSS selects an element, it will receive a set of style-rules. The "p" in the image below is a type of CSS selector because it targets the paragraph element, which will then receive style declarations, or CSS-rules.
3. A CSS-rule is a style declaration
This is a CSS rule:
<p> CODE: https://gist.github.com/jginbound/e0acf0f63260be4f3a892f851244b426.js</p>
CSS can apply more than one CSS-rule for any selector. Like this:
<p> CODE: https://gist.github.com/jginbound/e664cff3ba65d296aeb58984886a9097.js</p>
Now back to our example from above.
The paragraphs are purple with a bigger font-size. But we wanted them to be red and at 1rem to begin with- how does CSS decide which style takes precedence in the end?
The answer lies in the CSS cascade algorithm, which parses every CSS rule in the stylesheet from top to bottom.
For starters, CSS looks at the last style declaration in the stylesheet and resolves it as the "winning style" in the event that an HTML element has a style conflict. So in our paragraph example above, the purple color style with a font-size 2rem appears further down the stylesheet and therefore beats out the red and 1rem style rules.
This way of parsing styles from the top of a stylesheet to the bottom is known as the "cascade" because it resembles a waterfall that starts from the top and works its way down the source-order of the stylesheet.
That's a nice looking cascade...
But there's more to the cascade algorithm than just looking at the source order of CSS rules
As you'll see, the source-order of the cascade algorithm is just one out of two other important components in the algorithm.
Take a look at this example:
The paragraph color is black and has a font-size of 1.25. Shouldn't it be purple with a font-size of 2rem? Based on our understanding of how CSS applies styles based on where they appear in the source-order, the purple color style and font-size of 2rem appear later and should therefore be... purple!
So... how does the cascade algorithm really work?
We'll get there, I promise. In essence, CSS takes the stylesheet in question, parses it, and does more than just look at source-order. It asks the following questions:
Question 1: Are there any !important tags to look out for?
!important tags are really powerful- and with great power comes great (sometimes grave) consequences for your stylesheet. They can override any CSS-rule that you declare for any selector, regardless of where it is in the source-order.
Take a look at our button dilemma from before. This code shows us that even though the styles on line 9 appear later in the source-order, the CSS-rule on line 1 will take precedence, and that's due to the !important tag.
<p> CODE: https://gist.github.com/jginbound/b04a873e10d7b52ab5650bf0ddae0005.js</p>
Any CSS-rule with an !important tag will automatically take precedence in the end, regardless of where it is in the source-order of the stylesheet. End of story.
An !important tag is like that fancy VIP card that you flash at security to get in the front of the line. All of the other CSS selectors will be staring you down in envy while muttering underneath their breath, "psh there goes the !important tag, overriding everyone else..."
And while you flash that !important card, security (or the CSS parser in this case) will high-five you on your way in.
Should you use !important tags in your CSS code? Please don't. Look at what happened to our junior dev from our example above. !important tags will override any style on a selector and can cause major bugs in your code.
Here's what MDN (Mozilla Developer Network) has to say on the matter of !important tags:
Using !important, however, is bad practice and should be avoided because it makes debugging more difficult by breaking the natural cascading in your stylesheets.
Thank you, MDN- so we now understand that any CSS style rule with an !important tag will override any selector's style regardless.
But what if there aren't any !important tags in the stylesheet?
Take a look at this example from earlier:
<p> CODE: https://gist.github.com/jginbound/8fd16d45128737a7f2721eb14457a3e8.js</p>
This stylesheet does not have any !important tags.
At first glance, you'd be tempted to think that the paragraphs will be purple with a size of 2rem. But that's not the case as the styles found in lines 4-6 take precedence over the styles in lines 10-11.
This brings up the next question the cascade algorithm asks:
Question 2: How specific are the selectors?
CSS has several ways of referencing selectors.
We can go the plain-vanilla way and just reference its HTML element, like this:
Or with its class and/or pseudo-class like this:
We can dig deeper into specificity and reference a selector with its ID attribute, like this:
Lastly, we can declare CSS-rules within the HTML file. These CSS-rules are called "inline-styles" and they have the highest level of specificity:
Inline-styles are very specific and tend to override almost any other CSS-rule declared in a stylesheet. Take a look at this example:
The level of specificity of a CSS-rule is determined by the selector that targets it.
Specificity is measured in order of increasing importance:
- HTML elements or pseudo-elements (least specific)
- Classes or pseudo-classes (more specific)
- ID attributes (even more specific than)
- Inline-styles (most specific)
This lonesome <p> in line 1 isn't as specific as the one on line 6:
<p> CODE: https://gist.github.com/jginbound/9768349a2bfb17b7b1ef2fcb674de16e.js</p>
So is the level of specificity measured in some way?
I'm glad you asked! The CSS cascading-algorithm uses a point system to measure how specific a selector is.
The more specific a selector is, the more weight it receives for winning out its style-rule(s) in the end.
Again- CSS uses a point-based system to quantify how specific a selector is.
Let's start with the thousands slot
The thousands slot is the highest amount in the point system. Any CSS-rule that's declared as an inline-style wins the 1000 points, and is automatically awarded the style in the end.
Take a look again:
Because this CSS declaration was declared as an inline-style, it wins one spot in the thousands slot:
But please be aware that !important tags can override inline styles
Even though inline styles are the most specific of selectors, they can be overridden by the !important tag. Take a look:
Next on the list is the hundreds slot
Any CSS selector with an ID attribute gets a score in the hundred slot, just like this:
<p> CODE: https://gist.github.com/jginbound/d66b77c9272a8fb8377be9b7cd17f75b.js</p>
The ID attribute on line 1 has a specificity level of 100.
Next is the tens slot
Any selector that's invoked as a class, pseudo-class, or attribute will receive a spot in the tens slot.
Take a look at the markup found in this form element. Do you see any class selectors here?
These are class selectors, each which will be awarded 10 points on the specificity scale:
This is a pseudo-class selector which will also be awarded 10 points:
Lastly, this attribute selector will also be awarded 10 points on the specificity scale:
Phew. All of this sounds overly theoretical. The best way to understand specificity is with simple examples. So let's take a look at one:
<p> CODE: https://gist.github.com/jginbound/f0faba6682425a290e6e2ddb24cf1ed9.js</p>
The anchor link style gets overriden by the more specific .btn class, which then gets overriden by the even more specific .btn#cta selector.
Now, what happens if there are no !important tags and the specificity values of the selectors are the same?
Well in this case the CSS parses asks the final question in its algorithm:
Question #3: Where is the style applied in the source order of the stylesheet?
This one is really simple. If there are no !important tags and the specificity level is the same for the selector, then the selector that appears later in the stylesheet is the one who wins the style-rule in the end.
As MDN states, "Later rules win over earlier rules." Take a look:
Can you calculate the specificity score of the following selectors:
Here's what I got:
- p -> 1
- p#paragraph -> 101
- #button.btn:hover -> 120
If you have to take one thing away from this post
Please, please do not use !important tags in your CSS-rules. They will make it hard for other developers to debug your code as your stylesheets get larger in size.
Remember your rules to specificity and you'll be set to handle any styling conflicts along the way!