Specificity & the Cascade
When multiple CSS rules target the same element and set the same property, the browser must decide which value to apply. The cascade is the algorithm that makes this decision. Understanding it prevents the most frustrating CSS debugging sessions.
The cascade algorithm
The browser resolves conflicts in this order:
- Origin and importance -- where the style comes from and whether it uses
!important - Specificity -- how specific the selector is
- Source order -- which rule comes last in the code
If step 1 does not resolve the conflict, the browser moves to step 2. If step 2 is a tie, step 3 decides.
Step 1: Origin and importance
CSS rules come from three origins:
| Origin | Source | Priority (normal) | Priority (!important) |
|---|---|---|---|
| User agent | Browser defaults | Lowest | Highest |
| User | User stylesheets (rare) | Middle | Middle |
| Author | Your stylesheets | Highest | Lowest (among !important) |
In normal circumstances, your author styles override browser defaults. This is why you can change a link's colour from the default blue.
The !important keyword reverses the priority order within each origin. An !important browser default beats an
!important author style. But in practice, the important thing to remember is: !important author styles beat normal
author styles.
Step 2: Specificity
When two rules from the same origin target the same property, the one with higher specificity wins.
How specificity is calculated
Specificity is a four-part score: (inline, IDs, classes, elements)
| Component | What counts | Weight |
|---|---|---|
| Inline | The style attribute on an HTML element | 1,0,0,0 |
| IDs | Each #id in the selector | 0,1,0,0 |
| Classes | Each .class, [attribute], :pseudo-class | 0,0,1,0 |
| Elements | Each element, ::pseudo-element | 0,0,0,1 |
The score is compared left to right. A selector with any ID beats a selector with no IDs, regardless of how many classes or elements it has.
Specificity examples
| Selector | Specificity | Score |
|---|---|---|
p | (0,0,0,1) | 1 |
.intro | (0,0,1,0) | 10 |
p.intro | (0,0,1,1) | 11 |
#header | (0,1,0,0) | 100 |
#header .nav a | (0,1,1,1) | 111 |
#header .nav a:hover | (0,1,2,1) | 121 |
style="color: red" | (1,0,0,0) | 1000 |
Note: The "score" column is a simplified way to think about it, but specificity is not a single number. A selector with 11 classes (0,0,11,0) does not beat a selector with one ID (0,1,0,0). The components never carry over.
What does NOT affect specificity
- The universal selector (
*) has zero specificity :where()has zero specificity (by design):is()and:not()use the specificity of their most specific argument- Combinators (
>,+,~,) do not affect specificity - Media queries do not affect specificity
Step 3: Source order
When origin, importance, and specificity are all equal, the rule that appears last in the stylesheet wins:
.button {
color: blue;
}
.button {
color: red;
}
The button text is red. The second rule has the same specificity but comes later.
This also applies across stylesheets. If you load two CSS files, rules in the second file override rules in the first (when specificity is equal).
Inheritance
Some CSS properties are inherited by default -- child elements receive the parent's value without you writing a rule.
Inherited properties (partial list)
colorfont-family,font-size,font-weight,font-styleline-heighttext-align,text-transform,text-decorationletter-spacing,word-spacingvisibilitycursorlist-style
Non-inherited properties (partial list)
background,background-colorborder,border-radiuspadding,marginwidth,heightdisplay,positionbox-shadowoverflow
body {
color: #333;
font-family: sans-serif;
}
Every element inside <body> inherits color and font-family without you writing additional rules. But
background-color is not inherited -- children get a transparent background by default.
Forcing inheritance
Use the inherit keyword to force a non-inherited property to inherit:
.child {
border: inherit;
}
Other special values
| Value | Behaviour |
|---|---|
inherit | Use the parent's computed value |
initial | Use the property's default value (from the CSS specification) |
unset | Use inherit if the property inherits, otherwise initial |
revert | Roll back to the value from the previous cascade origin |
The !important keyword
Adding !important to a declaration gives it the highest priority within its origin:
.text {
color: red !important;
}
#header .text {
color: blue;
}
The text is red. Even though #header .text has higher specificity, !important overrides it.
Why to avoid !important
- It makes the cascade unpredictable -- other developers cannot override without their own
!important - It creates an escalation war:
!importantvs!importantis resolved by specificity again - It is a signal of a structural problem in your CSS
Legitimate uses of !important:
- Utility classes that must always win:
.hidden { display: none !important; } - Overriding third-party CSS you cannot modify
- Accessibility overrides (e.g., forced colours for readability)
Tip: If you find yourself reaching for
!important, step back and look for the specificity conflict. The solution is usually to simplify your selectors, not to escalate.
Cascade layers
CSS @layer (introduced in 2022) gives you explicit control over the cascade order of entire groups of rules:
@layer reset, base, components, utilities;
@layer reset {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
@layer base {
body {
font-family: sans-serif;
color: #333;
}
}
@layer components {
.button {
padding: 12px 24px;
background-color: #4a90d9;
color: white;
}
}
@layer utilities {
.hidden {
display: none;
}
}
The first line declares the layer order. Rules in later layers beat rules in earlier layers, regardless of
specificity. A simple .hidden in the utilities layer beats a complex #main .sidebar .widget.active in the
components layer.
This solves the specificity escalation problem: you no longer need to match or exceed the specificity of existing rules. You just put your override in a higher layer.
Note: Cascade layers are a more advanced topic. They are most useful in large projects and design systems. For small projects, understanding specificity and source order is sufficient.
Debugging specificity conflicts
When a style does not apply as expected:
- Open DevTools and inspect the element
- Look at the Styles panel -- it shows all matching rules, most specific at the top
- Crossed-out declarations are being overridden by higher-specificity rules
- Hover over a crossed-out rule to see what is overriding it
- If
!importantis involved, it will be visible in the Styles panel
Common fixes
| Problem | Fix |
|---|---|
| Class rule overridden by ID | Remove the ID selector; use a class instead |
| Rule not applying at all | Check if the selector actually matches the HTML |
| Inline style winning | Remove the inline style |
| !important blocking override | Add your own !important or refactor selectors |
| Source order conflict | Reorder your stylesheets or selectors |
Specificity strategy
Follow these guidelines to keep specificity manageable:
- Use classes for everything -- avoid IDs and element selectors for styling
- Keep selectors short --
.card-titleinstead of.main .content .card .card-title - Avoid nesting deeper than two levels --
.card .titleis fine,.page .section .card .titleis not - Never use !important for layout or cosmetic styles
- Use BEM or a similar naming convention to avoid selector conflicts (covered in chapter 16)
- Use cascade layers for large projects with multiple stylesheet sources
What you learned
- The cascade resolves conflicts in three steps: origin/importance, specificity, source order
- Specificity is scored as (inline, IDs, classes, elements) -- compared left to right
- Some properties inherit from parents (colour, font); others do not (margin, border)
!importantwins within its origin but causes maintenance problems -- avoid it- Cascade layers (
@layer) give explicit ordering that overrides specificity - DevTools show which rules win and which are overridden
Next step
Now that you understand how CSS resolves conflicts, the next chapter covers modern CSS features -- nesting, :has(),
container queries, and other recent additions to the language.