There’s a lot of hate out there for the cascade, and… I get it. We only notice the cascade when something goes wrong!
background: cyan
We’re just trying to style a web page,
give it a cyan background maybe –
and the background doesn’t change!
It’s still pink!
Maybe that’s a typo, or a modal property – but often when something doesn’t apply, it’s because of the cascade, right?
Does this feature only exist to stop us from styling a website? What’s the deal? Why’s it always getting in the way?
<button type="submit">…</button>
button {
background-color: deepPink;
}
button[type=submit] {
background-color: rebeccaPurple;
}
The cascade doesn’t get involved unless there’s a conflict styling the same property on the same element.
<body bgcolor="pink" text="#800000">
<font face="Arial" size="4" color="green">
<center>Every style is a one-off.</center>
</font>
</body>
Some authors will go to extremes to avoid these conflicts, and avoid the cascade. But the only way to do that is by applying styles directly on each element.
This was actually implemented by Netscape in 1994 –
again without warning or discussion.
Each style property has its own attribute,
mostly only on the body element,
and then new elements like font and center
make them work in other places.
This does a pretty good job of avoiding conflicts.
<body bgcolor="pink" bgcolor="cyan">
…
</body>
We could still set the bgcolor attribute multiple times,
but at least the conflict
is isolated to a single location, right?
If there’s a conflict on the body element,
we’ll find our problem right there on the body.
<p><font color="green">…</font><p>
<p><font color="green">…</font><p>
<p><font color="green">…</font><p>
<p><font color="green">…</font><p>
<p><font color="green">…</font><p>
<p><font color="green">…</font><p>
Over time this can become extremely repetitive and hard to maintain. There’s no such thing as a paragraph style or a button style, or a design system – only brute force copy and paste.
p { color: deepPink; }
.error { color: maroon; }
To create efficient design systems, we need to select and style multiple elements at once. Now there’s less repetition, but conflicts are basically un-avoidable.
button {
background-color: deepPink;
}
[type=submit] {
background-color: rebeccaPurple;
}
form:has(:invalid) [type="submit"] {
background-color: dimGray;
}
And the more meaningful those selectors become, the more useful it is to embrace conflicts as part of the system – styling elements at different levels of detail. First the broad defaults, then the common patterns, and finally more targeted overrides.

Harry Roberts calls this the inverted triangle of CSS. Our styles start with a broad reach, and become more narrowly targeted (and more explicit) as we go – with the majority of our code reaching as far as possible, and minimal code scoped to specific uses.
There’s more to this triangle, but at the core it’s a call to embrace systems, and treat conflict as an opportunity – something we should plan for.
background: cyan
When the cascade gives me a result that I’m not expecting, it’s not a failure of the cascade – that’s what it’s there for. This is an opportunity to clarify and strengthen the code.
I either need to more clearly scope my selectors to avoid an accidental conflict, or I need to more clearly layer my selectors so that the priority is clear.
Writing maintainable CSS is those two clarifications over and over.
So the goal of this module is to understand how conflicts work in CSS, and the tools that are built-in to help us manage those conflicts.
@@ merge conflict @@
<<<<<<< button
+background-color: deepPink;
======
+background-color: rebeccaPurple;
>>>>>>> button[type=submit]
We can think of conflicts in CSS as a form of merge conflict, similar to what we might see using version control. But instead of resolving conflicts line-by-line, we resolve them property-by-property.
And the browser has to figure out a winner on the fly, based on the information it has available.

In this algorithmic thunder dome – where two styles enter, and only one can leave.
- button { background-color: blue; }
button[type=submit] {
+ background-color: green;
}
The simplest rule would be something like last style wins.
That’s a great start, and it forms the base of the cascade. We call it order of appearance.
Technically that’s the last step of the cascade. When everything else fails, it’s guaranteed to give us a single answer. It doesn’t matter how many declarations make it to the end of the funnel – there can only be one declaration that comes last.
But it’s also the foundation of the cascade. Everything else is there to help us create more nuanced layering.
a practical assumption that approximates the goal
This is a heuristic – a practical simplification that gets us ‘close enough’ to an answer.
It is based on a straight-forward assumption that we’ll start with broadly-applied default styles, and then add more detailed styles for specific situations.
Work from broad defaults at the start, towards narrow overrides at the end.
So that’s our first rule. Start broad, get narrow.

Harry Roberts gave us the visualization, but the rule comes from CSS.
/* new syntax wins the cascade in new browsers */
article {
display: grid;
+ display: grid-lanes;
color: teal;
+ color: light-dark(teal, powderblue);
}
Order-of-appearance is also important for this progressive enhancement we discussed yesterday. We put the new feature last so that it wins the cascade in new browsers –
/* new syntax filtered out by older browsers */
article {
display: grid;
- display: grid-lanes;
color: teal;
- color: light-dark(teal, powderblue);
}
And is ignored by old browsers.
So order of appearance is great, but there’s a problem.

Because we know there are other people writing styles. So if the last style wins, then…

Who’s on first? And who gets the final say.
We can start with a pretty simple answer here. Browser defaults apply for everyone, so they go first. Then users can override those defaults to personalize their experience. User preferences are more specific – my preferences only apply to the sites I visit. And then author styles are the most narrowly targeted, only styling a single page at a time.
These are the normal cascade origins.
a practical assumption that approximates the goal
It’s another heuristic.
In this case it’s based on an assumption that most browser styles are just defaults, that we’re expected to override. And most user preferences are just personalized defaults. We choose fonts and colors that we like, but it’s fine if the web page provides something more explicit for the purpose.

Really it’s just the same assumption that more targeted styles should override broad defaults. But now we’re applying that logic to origins.

If we stop there website authors have way too much power!

If a trade-off needs to be made, always put user needs above all.
Some user preferences might actually be user needs, right? The user needs a certain font size or contrast ratio to even read the page.
And when it matters, users should get final say. That’s the rule.

While these heuristics may work fine most of the time, we need a way for users to say this preference is important and worth defending.
!important
In CSS,
that’s the !important flag.
It’s there as a manual override
when the assumptions fail.
Important styles don’t have higher specificity in the way selectors might – they get moved to entirely new origins!
Important styles always override normal styles, but they do it in reverse order. This time we go first, and the user needs take priority over our document styles.

!important behaviorHistorically, users could do that by writing CSS directly. Now most browsers provide a graphic interface, but they often still provide a checkbox to mark styles as important.

At the end, browsers can ensure that some things are off-limits. And they do. There are important styles throughout the browser stylesheets.
This is known as a ‘snake draft’ in games, a way of balancing power when taking turns. Instead of going around the circle, we turn around at the end, and go back in reverse order.
With our own colleagues and libraries
So importance isn’t designed as a way for us to win specificity wars against a tool like Bootstrap or a legacy style.
From higher origins breaking important things
It’s there to protect weak styles from future overrides.

And specifically to protect user styles from us!
Only use
!importantdefensively, on styles that should never get an override.
It’s fine for us to use, since it doesn’t impact the other origins. But we should only use it defensively. Let’s add that to our conventions. We’re not using importance to override something that already exists – we’re only using it to avoid future overrides.
[hidden] { display: none !important; }
For example,
I have this in my reset styles.
The hidden attribute should always
have display:none.
Nothing else we do should override that.
The only proper override
is removing the hidden attribute.
.color-blue { color: blue !important; }
Some people use this same logic for utility classes, and that makes some sense to me. What’s the point of a color utility if you can override its color?
But I also think utilities can work as a cascade layer, which we’ll get to later.
So the question is: what’s essential? What styles deserve protection? And I don’t think there’s a clean line.
Hiding the element seems essential to me
for an attribute called hidden.
So blue might be an essential part of a blue class.
Usually I wouldn’t consider
something like a grid-template to be essential,
but if we’re creating a specialized grid-layout component,
and that’s the entire reason to use it –
we might want to be defensive about that grid.
The entire cascade is built on applying these simple concepts at different levels of detail.
We’ll see this pattern over and over, as we build up more and more nuanced rules.
@@ merge conflict @@
<<<<<<< !important
+background-color: deepPink !important;
+background-color: powderBlue !important;
======
-background-color: teal;
-background-color: rebeccaPurple;
>>>>>>> normal
At each step, we’re just sorting our declarations into different buckets – everything in the top bucket moves on, everything in the bottom bucket gets discarded.

It’s like a mechanical coin sorter, but not just checking the coin’s diameter.

We have sorters for all these different criteria, and we move from one to the next until we get a winner.

At each stage we ignore any empty buckets, keep the styles that made it farthest, and eliminate everyone else.
Officially we have seven things to consider, but I’d break it down into 4 blocks.
The spec lists origins and importance as a single step, to highlight the snake-draft purpose of importance. But that reversal applies to several other steps as well.
So we can break that out. Important styles always override normal styles. Then we ask several questions about where a style came from – the origin, context, and element attachment. And then, if we used selectors, we can look at those as well – the cascade layer, specificity, and scope proximity. We’ll cover those in part two. And we’ve already talked about order of appearance.
The order matters. If we have a single important style, it wins, and we never have to think about the rest of the cascade. But I’m going out of order, because I think it helps us see how we got here.
Authors will sometimes use the term specificity referring to any part of this system. And if we’re being pedantic, only selectors have specificity.
But I think it’s also fair to say the whole system is designed around the concept of specificity. We always assume that more targeted styles should override more generic styles.
Users override browsers. Inline styles override selectors. Unique ID’s override reusable classes. More narrowly scoped styles override broadly scoped styles.
And these heuristics are surprisingly effective most of the time, But we only notice them when they fail. That’s not surprising. Assumptions always fail eventually.
Which brings us to a few extra secret origins that have been added over the years.
The first is called non-CSS presentational hints.
<div hidden>…</div>
<img src="…" alt="…" width="600" height="300">
That includes hidden,
or the width and height attributes on images,
or the deprecated bgcolor attribute
that Netscape added in 1994.
Technically, we control them using attributes. So they come from the ‘author’ origin. You won’t find them mentioned anywhere in the browser defaults.
Because they aren’t exactly default styles, right?
These presentational hints
are sometimes considered
part of or just before the normal author origin.
Our actual CSS will always override presentational hints –
which is why I move hidden styles
into my actual CSS.
We also have ‘intermediate’ or interpolated styles generated by animations & transitions.
hotPink
»
teal
If we’re transitioning between colors,
for example – hotpink to teal –
hotPink
rgb(93 138 220)
teal
The browser has to go through other colors
along the way.
Depending on the color space we use,
we might get this bluish color for a bit.
That color has to override both hotPink and teal,
or we’ll never see it applied.
button {
background: teal;
transition: background 1s;
}
button:hover { background: hotPink !important; }
For transitions, the start and end values have already survived the cascade. They might come from any origin, with any importance. The transition property also goes through that process, so we’ve all had our say, and we all agree it’s ok to proceed.
So transitional values have to override everything else. Transitions are the most powerful origin.
If you want to be evil with all this power, look into infinite transitions.
:target { animation: bg-change 3s ease-in both; }
@keyframes bg-change {
from { background: pink; }
to { background: cyan; }
}
Animations are a little different.
Only the animation property goes through the cascade.
Declarations inside a keyframe rule never do.
So animations get slotted in between the author origins – between normal and important styles. Important styles can’t be animated. They can only be transitioned.
And that’s the full list of origins! Browser defaults start us off, and then we build up all the way to transitions – each layer overriding the ones before.
Or we can think of that in two steps, if it helps.
If we’re only comparing normal styles at this step, then we move on –
To compare the normal origins.
And if we’re comparing important styles, the second step is reversed.
The next step of the cascade was added almost 20 years later, but follows exactly the same logic.
This is called Context in the cascade.
It’s like a sub-‘origin’,
but for styles that come from different
encapsulation contexts –
like the page vs shadow dom.
Same as origins, we get a default order where shadow DOM styles provide a default, which the host page can override.
But important styles are compared in reverse order.
/* shadow DOM styles */
:host(my-element) { outline: thin solid red; }
[part=title] { outline: thin dashed red; }
::slotted(span) { outline: thin dotted red; }
/* light DOM styles */
my-element { outline: thick solid green; }
my-element::part(title) { outline: thick dashed green; }
my-element > span { outline: thick dotted green; }
But the whole point of encapsulation is hiding things from outside selectors. So these conflicts only really happen on the host element, or exposed parts.
and reverses priority
Again, importance is defensive – allowing component authors to protect essential styles.
(demo if needed)
The next in our “where do styles come from” group is about how styles are attached to elements (like inline styles) or mapped to elements using selectors.
Which of those sounds more specific?
By default, inline styles win against selector-mapped styles. Selectors can win by adding importance, but this time the order doesn’t reverse. Inline important styles are the most powerful.
Sometimes, if you’re working on a project where the HTML is out of your control, you might need importance to override inline styles. I know this used to be an issue in Drupal years ago.
But if you can edit the HTML, I think there’s a better solution – using inline variables.
<span style="background: blue">not great</span>
<span style="--tag-color: blue">much better!</span>
Sometimes it’s useful to put styles inline, if they represent data from the server – like certain colors associated with tags in a CMS.
If we set normal properties, that can be hard to override – but if we set variables, there’s no reason to override it. That variable doesn’t style anything until we apply it in CSS – however and whenever we want to.
You don’t need to override something when you can simply ignore it.
(demo if needed)
Note that cascade rules higher up in this funnel will take priority over rules lower in the funnel. Importance matters more than context or element attachment – because importance is considered first. By the time we get around to asking about selector specificity, we’ve already resolved the question of importance.
When we’re afraid of the cascade, every potential conflict becomes another CSS feature to avoid. Never use importance, never use ID or element selectors, never use inline styles, never leave the house, or touch grass, or try something new.
But all these features are useful!

It’s like telling a woodworker they should avoid using anything sharp because they might cut something by mistake.
That’s the whole point! If you want to work with wood, get out there and cut things! Just… you know, learn how to use the saw first.
The next three steps in the cascade – layering, specificity, and scope proximity – are all about selectors! So if two element-attached styles conflict with the same importance, we can skip all the way to the end of the cascade, and resolve that conflict using the order of appearance.
The rest of this module is all about selector-mapped styles. And even though layering takes priority, I want to look at specificity and scope first.