← Back to Index

Lesson 14

The Cascade - The Complete Resolution Algorithm

Everything you've learned - layers, specificity, selectors, custom properties, inheritance - is part of one unified system: the cascade. This lesson puts it all together into a single mental model you can use to predict exactly which style wins in any situation.

The cascade answers one question

When multiple CSS declarations target the same property on the same element, which one applies? The cascade resolves this by checking a series of criteria in order. The first criterion to produce a winner ends the comparison.

Mental model: Think of it as a tournament bracket. Competing declarations are eliminated round by round. Each round is a different criterion. Most conflicts are resolved in the early rounds - you rarely need to go all the way to the end.

The resolution order - all 8 steps

When two or more declarations compete for the same property on an element, the browser resolves them in this order (first decisive step wins):

1. Origin & Importance

Where did the style come from, and is it !important?

Priority (highest → lowest): transition declarations > user-agent !important > user !important > author !important > author normal > user normal > user-agent normal

2. Inline styles

style="..." on the element beats any selector-based rule (within the same origin/importance tier).

3. Layers

Unlayered > last declared layer > ... > first declared layer. For !important: inverted (first layer wins).

4. Specificity

Compare selector specificity: (ID count, class/attribute/pseudo-class count, element/pseudo-element count). Higher wins.

5. Scope proximity (new, with @scope)

Closer scope root wins. Only relevant when using @scope.

6. Source order

Last declaration in source order wins. This is the final tiebreaker.

In practice: Most conflicts you encounter are resolved at step 3 (layers) or step 4 (specificity). If you use layers properly, you'll rarely even need to think about specificity. If you don't use layers, specificity is your main battleground.

Step 1: Origin & Importance - who wrote it?

CSS comes from three "origins":

Within author styles, !important flips the priority:

/* Normal author styles  -  resolved by later steps (layers, specificity, order) */
.card { color: blue; }

/* !important author styles  -  beat ALL normal author styles regardless of specificity */
.text { color: red !important; }

/* Even this loses to !important above, despite higher specificity: */
#main .content .card .title { color: green; }  /* loses to red !important */

Why !important is a last resort: It jumps an entire tier in the cascade. Once you use it, the only way to override it is with another !important in a higher-priority layer or a later source order position. This creates escalation wars. Use layers instead.

Step 2: Inline styles

The style="" attribute on an element beats any selector-based rule in the same origin/importance tier:

The inline style (red) beats the stylesheet rule (blue), despite .card being a valid selector

I'm red because of style="color: red" - the stylesheet says blue but inline wins.
<!-- HTML -->
<div class="card" style="color: red;">I'm red</div>

/* This loses to the inline style: */
.card { color: blue; }

/* Only !important can beat inline: */
.card { color: blue !important; }  /* this WOULD win */
Ad

Step 3: Layers (you learned this in Lesson 11)

Quick recap of how layers interact with the rest of the cascade:

/* Resolution order for normal styles: */
Unlayered styles (no @layer)     ← WINS over all layers
@layer utilities { }             ← wins over components
@layer components { }            ← wins over base
@layer base { }                  ← wins over reset
@layer reset { }                 ← lowest priority

/* For !important: INVERTED */
@layer reset { !important }      ← WINS (highest priority)
@layer base { !important }
@layer components { !important }
@layer utilities { !important }
Unlayered !important             ← lowest for !important

Step 4: Specificity - the scoring system

Specificity is a three-part score written as (A, B, C). Each position counts a different category of selector part:

Specificity = ( A,          B,                        C                    )
                ↑            ↑                         ↑
           count of     count of                  count of
           #id          .class                    element
                        [attribute]               ::pseudo-element
                        :pseudo-class
                        (:hover, :not, :has, :is)

Calculate it by counting each part of your selector and placing the count in the right column:

h1                        → 0 IDs, 0 classes, 1 element     = (0, 0, 1)
.card                     → 0 IDs, 1 class,   0 elements    = (0, 1, 0)
.card .title              → 0 IDs, 2 classes, 0 elements    = (0, 2, 0)
#main .card               → 1 ID,  1 class,   0 elements    = (1, 1, 0)
#main .card .title:hover  → 1 ID,  3 (card+title+hover), 0  = (1, 3, 0)
div.card[data-v]          → 0 IDs, 2 (class+attr), 1 elem   = (0, 2, 1)

/* Functional pseudo-classes  -  specificity from their argument: */
:is(.card, #main)         → (1, 0, 0)  /* takes highest argument (#main) */
:where(.card, #main)      → (0, 0, 0)  /* :where is ALWAYS zero */
:not(.disabled)           → (0, 1, 0)  /* specificity of its argument */

Comparison rule: Read left to right. The first column where one score is higher wins. Columns don't add up across positions - (1, 0, 0) beats (0, 99, 99) because one ID outranks any number of classes. Think of it like version numbers: 2.0.0 > 1.99.99.

The scoring rules:

  • Compare left to right: IDs beat any number of classes. Classes beat any number of elements.
  • (1, 0, 0) beats (0, 99, 99) - one ID outweighs 99 classes.
  • If tied at every position, fall through to step 6 (source order).

Step 6: Source order - the final tiebreaker

If origin, importance, layers, and specificity are all equal, the last declaration in source order wins:

Same specificity, same layer - last one in the file wins (coral)

I'm coral - the second .box rule wins by source order.
<!-- HTML -->
<div class="box">What colour am I?</div>

/* Same specificity (0,1,0)  -  source order decides */
.box { background: blue; }    /* first  -  loses */
.box { background: coral; }   /* last  -  wins */

This is why CSS files loaded later in the <head> override earlier ones - and why putting your custom stylesheet after a framework's stylesheet lets you override it (assuming equal specificity).

Inheritance - the other way properties get values

The cascade resolves conflicting declarations. But what about properties with NO declaration at all? That's where inheritance comes in.

Some properties inherit by default - if not explicitly set, they take the parent's computed value:

The mental model: "Text-related" properties generally inherit (because you want text styling to flow down). "Box-related" properties don't (because you don't want every child to have the parent's padding).

Controlling inheritance explicitly

.child {
  color: inherit;    /* force inheritance (even for non-inheriting properties) */
  border: initial;   /* reset to the property's default value */
  padding: unset;    /* = inherit for inheriting properties, initial for non-inheriting */
  all: revert;       /* reset ALL properties to browser default (user-agent stylesheet) */
  all: revert-layer; /* reset to the previous layer's value */
}

Putting it all together - a worked example

Which colour wins? Walk through the cascade steps.

I'm teal. See the walkthrough below.
<!-- HTML -->
<p id="intro" class="text highlight" style="color: pink;">What colour?</p>

/* Competing declarations for color on this element: */
@layer base {
  p { color: gray; }                              /* (0,0,1) in base layer */
}

@layer components {
  .text { color: blue; }                          /* (0,1,0) in components layer */
  #intro { color: red; }                          /* (1,0,0) in components layer */
}

.highlight { color: teal !important; }            /* (0,1,0) unlayered !important */

/* Resolution:
   Step 1: !important beats all normal rules → "teal !important" vs "pink" (inline)
           !important author beats inline (unless inline is also !important)
           → teal wins.

   Without the !important, inline "pink" would win (step 2).
   Without inline, the layer step would matter (unlayered > components > base).
   Within the same layer, #intro (1,0,0) beats .text (0,1,0).
*/

The complete picture - one diagram

CASCADE RESOLUTION ORDER First decisive step wins - later steps are never consulted 1. Origin & Importance transitions > UA !important > user !important > author !important > author > UA 2. Inline styles style="" beats any selector-based rule in same tier 3. Layers Normal declarations: unlayered > last layer > ... > first layer Declarations with !important (priority reverses): first layer > ... > last layer > unlayered 4. Specificity (IDs, Classes, Elements) - compare left to right, higher wins 5. Scope proximity closer @scope root wins (only with @scope) 6. Source order last declaration in the file wins (final tiebreaker) 7. No declaration at all? Inheriting property (color, font-size...) → parent's computed value Non-inheriting property (padding, border...) → initial value Cascade steps (competing declarations) Inheritance fallback (no declaration exists) Newer / less common (may not apply)

The practical takeaway: If you use @layer properly, you almost never need to think about specificity. If you keep specificity flat (BEM or similar), you almost never need to think about source order. The cascade gives you multiple tools - you only need to master the level of control your project requires.

Retrieval check

Question 1

In the cascade resolution order, what is checked BEFORE specificity?

Source order
Inheritance
Origin/importance, inline styles, and layers
Selector complexity

Question 2

An element has style="color: blue" and a stylesheet rule .card { color: red !important }. Which wins?

Inline (blue) - inline always beats stylesheets
!important (red) - importance beats inline
Neither - they cancel each other out
Depends on specificity of .card

Question 3

Selector A has specificity (0, 3, 0). Selector B has specificity (1, 0, 0). Same layer, same origin. Which wins?

A - higher total count (3 vs 1)
B - one ID beats any number of classes
Tie - need to check source order
Depends on which came later in the file

Question 4

A property has no declaration on an element. It's an inheriting property (like color). What value does it get?

transparent - the default for all CSS
The initial value defined in the spec
The parent element's computed value
It remains undefined until JavaScript sets it

Question 5

Two rules in the same layer have the same specificity. How is the winner determined?

The one with more properties wins
They merge - both apply
It's random - depends on browser
Last in source order wins