back to module

Smashing Workshops

Balance of Concerns

Arrows navigate, cmd/ctrl-k for settings

The Cascade Sucks ⁉️

Big, if true

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?

The Cascade Sucks ⁉️

Big, if true

Does this feature only exist to stop us from styling a website? What’s the deal? Why’s it always getting in the way?

html
<button type="submit">…</button>
css
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.

html
<body bgcolor="pink" text="#800000">
  <font face="Arial" size="4" color="green">
    <center>Every style is a one-off.</center>
  </font>
</body>
Was Netscape right all along?

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.

html
<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.

html
<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.

css
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.

css
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.

Inverted triangle with
range across the top,
and specificity/explicitness
down the sides
2014 [Harry Roberts, Inverted Triangle CSS]

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.

Clarify The scope or layer

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.

Use the cascade As a Tool Kit

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.

diff
@@ 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.

Tina Turner as Aunty Entity in the Mad Max Thunderdome, with the law 'Two styles enter, one style leaves' in bold text
Put Tina in the browser, you cowards

In this algorithmic thunder dome – where two styles enter, and only one can leave.

diff-css
- button { background-color: blue; }
  button[type=submit] {
+   background-color: green;
  }

The simplest rule would be something like last style wins.

The Cascade (as a funnel): present, debug

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 “Heuristic”

a practical assumption that approximates the goal

Heuristic on Wikipedia

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.

— Cascading Conventions

So that’s our first rule. Start broad, get narrow.

Inverted triangle with
range across the top,
and specificity/explicitness
down the sides
2014 [Harry Roberts, Inverted Triangle CSS]

Harry Roberts gave us the visualization, but the rule comes from CSS.

diff-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 –

diff-css
/* 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.

Big 3d text in space says we're not alone.

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

Abbot and Costello
wearing baseball uniforms
under a sign that asks
who's on first

Who’s on first? And who gets the final say.

  1. User Agent Defaults
  2. User Preferences
  3. Author Styles
Normal Cascade Origins

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 “Heuristic”

a practical assumption that approximates the goal

Heuristic on Wikipedia

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.

Inverted triangle with
range across the top,
and specificity/explicitness
down the sides
2014 [Harry Roberts, Inverted Triangle CSS]

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

Darth Vader
choking a rebel soldier
as storm troopers watch

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

avatar

If a trade-off needs to be made, always put user needs above all.

— W3C, Platform Design Principles

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.

Kylo Ren and Rey
standing in front of an entire star war
with spaceships and lasers
and AT-AT walkers

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.

  1. User Agent Defaults
  2. User Preferences
  3. Author Styles
  4. !Author Styles
  5. !User Preferences
  6. !User Agent Defaults
Importance Cascade Origins

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.

In the font preferences dialogue, a default-checked option: Allow pages to choose their own fonts, instead of your selections above
Firefox: un-check for !important behavior

Historically, 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.

Searching firefox user agent styles for important with 26 results, three visible setting area display none and fullscreen iframes border none, padding unset

At the end, browsers can ensure that some things are off-limits. And they do. There are important styles throughout the browser stylesheets.

Snake Draft

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.

Authors 👎🏼 Specificity Wars

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.

Browsers/Users 👍🏼 Protect Styles

From higher origins breaking important things

It’s there to protect weak styles from future overrides.

Darth Vader
choking a rebel soldier
as storm troopers watch

And specifically to protect user styles from us!

Only use !important defensively, on styles that should never get an override.

— Cascading Conventions

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.

css
[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.

css
.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.

What’s Essential?

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 Cascade (as a funnel): present, debug

The entire cascade is built on applying these simple concepts at different levels of detail.

  • We start with a heuristic based on how narrowly targeted (or specific) a style is expected to be
  • And then we add a manual override, which sometimes triggers a snake draft

We’ll see this pattern over and over, as we build up more and more nuanced rules.

diff
@@ 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.

Mechanical coin sorter on a table, with a ramp at the top and a small-to-large series of slots

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

Mechanical coin sorter duplicated multiple times in semi-transparent overlays

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

Same coin sorter, with all slots crossed off except for the largest one with coins in it, and an arrow pointing at those coins that says 'move on'

At each stage we ignore any empty buckets, keep the styles that made it farthest, and eliminate everyone else.

The Cascade (as a funnel): present, debug

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.

  1. Is it Important?
  2. Where did it Come From?
  3. How was the element Selected?
  4. Last style wins.

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 Cascade (as a funnel): present, debug

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.

All these heuristics Reflect “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.

Secret Origins

Which brings us to a few extra secret origins that have been added over the years.

Non-CSS Presentational Hints

The first is called non-CSS presentational hints.

html
<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?

  1. User Agent Defaults
  2. User Preferences
  3. (presentational hints)
  4. Author Styles
  5. !Author Important
  6. !User Important
  7. !User Agent Important
Importance Cascade Origins

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.

Intermediate styles From Transitions & Animations

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.

css
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.

  1. User Agent Defaults
  2. User Preferences
  3. (presentational hints)
  4. Author Styles
  5. !Author Important
  6. !User Important
  7. !User Agent Important
  8. Transitions

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.

css
: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.

  1. User Agent Defaults
  2. User Preferences
  3. (presentational hints)
  4. Author Styles
  5. Animations
  6. !Author Important
  7. !User Important
  8. !User Agent Important
  9. Transitions

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.

  1. Normal Styles
  2. Animations
  3. Important Styles
  4. Transitions

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 –

  1. User Agent Defaults
  2. User Preferences
  3. Author Styles

To compare the normal origins.

And if we’re comparing important styles, the second step is reversed.

The Cascade (as a funnel): present, debug

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.

  1. Encapsulated context (shadow dom)
  2. Host context (page)

Same as origins, we get a default order where shadow DOM styles provide a default, which the host page can override.

  1. !Host important
  2. !Encapsulated important

But important styles are compared in reverse order.

css
/* 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.

Again… Importance is Defensive

and reverses priority

Again, importance is defensive – allowing component authors to protect essential styles.

Important Shadow Context: present, debug

(demo if needed)

The Cascade (as a funnel): present, debug

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.

Prefer… Inline Variables

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.

html
<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.

Element-Attached (Inline) Styles : present, debug

(demo if needed)

The Cascade (as a funnel): present, debug

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.

No Abstinence Rules

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!

Someone cutting wood
with a hand saw.

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 Cascade (as a funnel): present, debug

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.

Next module… Meaningful Selectors

« back to resources
SlideDeck Controls

View

Keyboard shortcuts
cmd/ctrl-k:
Toggle this control dialog
cmd/ctrl-shift-enter:
presentation (from first slide)
cmd/ctrl-enter:
presentation (from active slide)
alt-enter:
in speaker view (following active slide)

Color Settings