Precedence in CSS (When Order of CSS Matters)
On your average CSS-writin’ day, odds are you won’t even think about precedence in CSS. It doesn’t come up a whole heck of a lot. But it does matter! It comes up any time multiple CSS selectors match and element with the exact same specificity.
Assuming specificity is exactly the same, order does matter.
Styles declared later win.
Within a single stylesheet
Say we have some HTML like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="module module-foo module-bar">
Module
</div>
</body>
</html>
The order of the attributes in the HTML don’t matter. It’s the order in the stylesheet. Let’s focus on the background
:
/*
All of these selectors match
and they all have the same specificity
*/
.module {
background: #ccc;
padding: 20px;
max-width: 400px;
margin: 20px auto;
}
.module-foo {
background: orange;
}
/* LAST declared, so these property/values win */
.module-bar {
background: lightblue; /* I win! */
/* I still have all the _other_ styles as well */
}
An intentionally convoluted example
Order is not limited to a single stylesheet. The order of the stylesheet in the document matters even more.
Check out this document with three distinct style… uh… let’s call them chunks. A chunk being either a , a
block, or an
@import
ed stylesheet.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- #1 -->
<link rel="stylesheet" href="1.css">
<!-- #2 -->
<style>
.module-baz {
background-color: pink;
}
</style>
</head>
<body>
<div class="module module-foo module-bar module-baz">
Module
</div>
<!-- #3 -->
<style>
@import "2.css";
/*
Contains
.module-bar { background: #f06d06; }
*/
</style>
</body>
</html>
I labeled the chunks #1, #2, and #3. All of them contain CSS selectors with the same exact specificity. #3 is the last declared, so it wins the precedence battle.
Async loaded CSS still respects document order
Perhaps you’re loading CSS with an awesome CSS loader like loadCSS. What happens if we were to load a fourth CSS file with it with the exact same setup as the “convoluted” example above?
loadCSS injects the stylesheet at the bottom of the by default, so it would become #3 and the
block at the bottom of the body would become #4 and thus win.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="1.css">
<script src="loadCSS.js"></script>
<script>
loadCSS("2.css");
</script>
<!-- 2.css will be injected right here -->
</head>
<body>
<div class="module module-foo module-bar module-late">
Module
</div>
</body>
</html>
It’s actually invalid (although it works) to have a or
block as a child of the
, so it would be really rare for a stylesheet loaded by loadCSS to not be the winner by default.
Also, you can specify an ID to target so you can control CSS order:
<script id="loadcss">
// load a CSS file just before the script element containing this code
loadCSS("path/to/mystylesheet.css", document.getElementById("loadcss"));
</script>
Does Critical CSS get weird?
One of the reason you might use loadCSS at all is because you’re intentionally trying to defer loading of your stylesheet, because you’re injecting critical CSS into the to try and get styles into the first packet and speed up rendering.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
/* #1
Critical CSS chunk up here */
</style>
<script>
/* #2
Load the rest of the CSS
*/
</script>
</head>
<body>
<div class="module module-foo module-bar">
Module
</div>
</body>
</html>
The practice of critical CSS involves moving up CSS selectors into a higher chunk. The #1 chunk. The lowest-order and easiest-to-override chunk. So, theoretically, yes, there could be conflicts/changes in what CSS gets applied when comparing the page with just the critical CSS applied and with the CSS fully loaded. But the stylesheet does fully load, and comes after the critical CSS, so it will ultimately be as-intended.
You might might need to fine-tune exactly what makes it into critical CSS and what does not – to avoid weird flash-of-style-changes.
Do extends get weird?
In a preprocessor, they can.
Say you want to style a thing with a variation:
<div class="module module-variation">Module</div>
And the (super simplified for demo purposes) preprocessor code ends up like:
%variation {
background: orange;
}
.module {
background: #ccc;
padding: 20px;
max-width: 400px;
margin: 20px auto;
}
.module-variation {
@extend %variation;
}
You’d think… OK, .module-variation
is the LAST declared selector, so it wins, so the background should be orange. But it’s not, because the extend moves the selector to where the thing it’s extending is defined, which in our case is before what we are trying to override. The compiled CSS is:
.module-variation {
background: orange;
}
.module {
background: #ccc;
padding: 20px;
max-width: 400px;
margin: 20px auto;
}
So the background
is actually #ccc
, not what we want. Probably easiest to solve this with specificity rather than source order.
Native extends, should they become a thing, would presumably be less confusing.
It’s a silly thing to manage
Nobody wants to think about this. Winning style with specificity is way easier. But knowing about it is a good idea, because unnecessary specificity bloat is a bummer too.
And on we dance.
Precedence in CSS (When Order of CSS Matters) is a post from CSS-Tricks