Intermediate40 minCSSDebugging

Cascade, Specificity, and Debugging CSS

Understand why styles collide, inherit, or lose, then debug those conflicts with more confidence.

Learning Objectives

By the end of this lesson, you'll be able to:

  • Explain Explain the four layers of the cascade: origin, specificity, source order, and inheritance
  • Calculate specificity scores and predict which selector wins a conflict
  • Distinguish inherited properties from non-inherited ones
  • Use browser DevTools to identify why a style is overridden
  • Apply Apply practical strategies to write lower-specificity, more maintainable CSS

Why This Matters:

Debugging CSS is not about finding tricks. It is about understanding the system. When you know how the cascade works, you stop guessing and start reading the browser's decisions.

Before You Start:

You should be familiar with:

The Cascade: How the Browser Decides

CSS stands for Cascading Style Sheets. The cascade is the algorithm the browser uses to decide which CSS rule wins when more than one rule targets the same property on the same element.

The browser checks four things, in order. As soon as one layer produces a clear winner, the browser stops and applies that rule.

Vertical flow diagram showing the four cascade layers: Origin and Importance, Specificity, Source Order, and Inheritance, checked top to bottom.
The cascade checks origin first, then specificity, then source order. If no rule targets the property directly, the browser checks whether the property inherits from a parent.

The Four Cascade Layers

  1. Origin and importance — browser defaults, your stylesheet, and !important declarations are ranked. Your author styles override browser defaults. !important overrides everything else.
  2. Specificity — among rules from the same origin, the more specific selector wins. This is where most conflicts are decided.
  3. Source order — if two rules have the same origin and specificity, the one that appears later in the stylesheet wins.
  4. Inheritance — if no rule targets the property directly, some properties (like color and font-family) inherit from the parent element.

In practice, most CSS conflicts come down to specificity and source order. If you can read those two layers, you can debug most style problems.

Specificity: Why Some Selectors Win

Specificity is how the browser ranks selectors. Each selector gets a score split into three columns:

  • Column A — IDs. Each #id in the selector adds 1 to this column.
  • Column B — Classes, attributes, and pseudo-classes. Each .class, [attribute], or :hover adds 1 here.
  • Column C — Element types and pseudo-elements. Each p, nav, or ::before adds 1 here.

The browser compares specificity column by column, from left to right. The first column where one selector scores higher is the winner. This means one ID will always beat any number of classes.

Table showing five selectors with their specificity scores in three columns: IDs, Classes, and Elements. Selectors are ordered from lowest to highest specificity.
Specificity is not a single total — it is a three-column comparison. One ID outweighs any number of classes.

Working through examples

Let's score some real selectors:

/* 0-0-1 — one element selector */
p { color: grey; }

/* 0-1-0 — one class selector */
.warning { color: orange; }

/* 0-1-1 — one class + one element */
nav .site-nav__link { color: #1f2933; }

/* 1-1-0 — one ID + one class */
#main-nav .link { color: #8f2f23; }

/* 1-0-1 — one ID + one element */
#header a { color: #333; }

If .warning and p both target the same paragraph, .warning wins because 0-1-0 beats 0-0-1 in the class column. Source order does not matter here — the class always wins.

Watch out for specificity escalation. If you fix a conflict by making a selector more specific, the next conflict might need an even more specific selector. This arms race is how stylesheets become hard to maintain. Prefer keeping selectors at a consistently low specificity instead.

Where !important fits

!important jumps ahead of normal specificity entirely. A declaration marked !important beats any normal declaration, regardless of selector specificity.

/* This wins over even #header #nav a.active */
.site-nav__link {
  color: navy !important;
}

That sounds useful, but it is a trap. If two rules both use !important, they fall back to the normal cascade — and now both sides of the conflict are harder to override. Use !important only as a last resort, and treat it as a code smell that suggests the specificity structure needs rethinking.

Inheritance: What Passes Down the Tree

When no CSS rule directly targets a property on an element, some properties inherit the value from the nearest ancestor that has one. This is how you can set font-family on the <body> and have every paragraph, heading, and list item use that font without extra rules.

But not all properties inherit. Layout and box-model properties like border, padding, margin, and display do not inherit — otherwise every child element would duplicate its parent's border, which would be chaos.

Tree diagram showing font-family inheriting from body down through section to paragraph and heading, alongside a second branch showing border on a div NOT inheriting to its child paragraph.
Text properties like font-family and color flow down the tree. Layout properties like border and padding stop where they are set.

Common Inherited vs Non-Inherited Properties

Inherits ✓Does not inherit ✗
colorborder
font-familypadding
font-sizemargin
line-heightwidth / height
letter-spacingdisplay
text-alignbackground

Controlling inheritance explicitly

You can force or remove inheritance with the inherit, initial, and unset keywords:

/* Force a non-inheriting property to inherit */
.child {
  border: inherit;
}

/* Reset a property to the browser default */
.reset {
  color: initial;
}

/* If the property normally inherits, act like inherit.
   If it does not, act like initial. */
.flexible {
  all: unset;
}

These keywords are most useful for debugging or for resetting styles in a component boundary. You do not need them in everyday CSS.

Common Style Conflicts and Why They Happen

Most CSS frustration comes from a small set of recurring patterns. Once you recognise them, the mystery disappears.

Conflict 1: A class rule loses to a more specific selector

/* 0-1-0 */
.button { background: navy; }

/* 0-1-1 — wins because of the extra element selector */
section .button { background: grey; }

The second rule is more specific. The fix is usually to keep both selectors at the same specificity — for example, by removing the unnecessary parent section from the second rule.

Conflict 2: Source order overrides what you expect

/* Both have specificity 0-1-0 */
.card { padding: 2rem; }

/* This one wins because it comes later */
.card { padding: 1rem; }

When specificity is tied, the last rule in the file wins. The fix is to remove the duplicate or organise the stylesheet so the intended value appears last.

Conflict 3: An inherited value is mistaken for a direct rule

body { color: #333; }

/* The .card has no color rule, but text appears grey
   because it inherits color from body.
   Adding a direct rule will override the inheritance. */
.card { color: #1f2933; }

Inheritance is not a bug — it is the expected behaviour. But it can be confusing if you do not realise the value is coming from an ancestor.

Conflict 4: !important blocking a later fix

.nav-link {
  color: navy !important;
}

/* This does NOT work — !important can only be overridden
   by another !important with higher specificity */
.nav-link.is-active {
  color: #8f2f23;
}

The solution is to remove the !important from the first rule and let normal specificity handle the conflict.

Debugging with DevTools

The browser's DevTools Styles panel is the single most useful tool for CSS debugging. It shows you exactly what the browser sees: which rules target the selected element, which values are applied, and which are overridden.

Simplified DevTools Styles panel showing a winning rule with specificity 0-2-0 and a losing rule with specificity 0-1-0 whose color value is struck through, with annotations explaining why the browser chose one over the other.
The Styles panel lists rules from highest to lowest priority. Overridden values are struck through. Click the filename to jump to the source.

Step-by-step DevTools debugging

  1. Inspect the element. Right-click the element in the page and choose Inspect. The Elements panel highlights the element in the DOM tree and the Styles panel shows all rules targeting it.
  2. Read the Styles panel top to bottom. Rules are listed from highest priority to lowest. The winning declaration is the one that is not struck through.
  3. Look for strikethroughs. A struck-through value means another rule overrides it. The winning rule is above it in the panel.
  4. Check the Computed tab. Switch to the Computed tab to see the final resolved values the browser is actually using, including inherited values and their source.
  5. Click the source link. Each rule shows a filename and line number. Click it to jump directly to the rule in the source code.

Computed tab tip: the Computed tab is especially useful for tracing inheritance. It shows where a value came from — whether it was set directly or inherited from an ancestor.

Practical Strategies for Maintainable CSS

Understanding the cascade is not just for debugging. It also helps you write CSS that causes fewer conflicts in the first place.

Keep Specificity Low and Flat

If most selectors in your stylesheet are single classes (0-1-0), conflicts are decided by source order, which is easy to control. Avoid nesting selectors unnecessarily.

/* ❌ High specificity — hard to override later */
#sidebar .widget ul li a { color: grey; }

/* ✅ Low specificity — easy to override with another class */
.widget-link { color: grey; }
.widget-link.is-active { color: navy; }

Use Source Order Intentionally

Organise your stylesheet in layers: resets first, then foundations, then layout, then components, then overrides. That way, later rules naturally override earlier ones when specificity is equal.

/* 1. Reset / foundations */
/* 2. Layout helpers */
/* 3. Components */
/* 4. Page-specific overrides */

Avoid !important Except for Utilities

The one reasonable use of !important is for small utility classes that must always win — like a visually-hidden class or a print-only override. Everywhere else, prefer fixing the specificity structure.

Name Components for Purpose, Not Position

Class names that describe what a component does (.menu-card) are easier to reuse than names that describe where it sits (.sidebar-box). Purpose-based names also tend to keep specificity flat because they do not need parent selectors.

⏸️ Pause & Check: Do You Understand?

Before moving forward, can you answer these?

  1. Two rules target the same element. One uses a class selector, the other uses an element selector. Which one wins and why?
  2. You set font-family on the body element, but you never set it on a paragraph inside a section. Why does the paragraph still use that font?
  3. A developer adds !important to fix a style conflict. What is the risk of that approach?
  4. In DevTools, you see a CSS property value with a strikethrough. What does that tell you?
Check Your Answers
  1. The class selector wins because it has higher specificity (0-1-0 vs 0-0-1). Specificity is compared column by column from left to right, and the class column is checked before the element column.
  2. Because font-family is an inherited property. When no direct rule sets it, the paragraph inherits the value from its nearest ancestor that has one — in this case, body.
  3. !important overrides the normal cascade, so it wins regardless of specificity. The risk is that future rules also need !important to override it, creating an arms race that makes the stylesheet harder to debug and maintain.
  4. It means another rule with higher specificity, later source order, or !important is overriding that property. The rule above the struck-through one in the Styles panel is the winner.

How confident are you with this concept?

😕 Still confused | 🤔 Getting there | 😊 Got it! | 🎉 Could explain it to a friend!

Debug a Style Conflict

Use DevTools to find, diagnose, and fix a real CSS conflict in your own project.

Find the conflict in DevTools

Open any web page you have been working on — your Black Swan Bistro project is ideal. Right-click an element that has a colour or font style and choose Inspect.

In the Styles panel, look at the list of rules targeting that element. Can you find a property that appears in more than one rule? If so, one of them should be struck through.

💡 Need a hint?
Navigation links are a good place to start because they often have hover states and layout rules from multiple selectors.
If nothing is struck through, try adding a temporary rule in the Styles panel to create a conflict on purpose.

Read the specificity

For the winning rule, count its specificity score using the three-column method: IDs, classes, elements. Do the same for the losing rule.

Write the scores on paper or in a code comment. Does the higher-specificity rule match what DevTools is showing as the winner?

💡 Need a hint?
Pseudo-classes like :hover count in the class column.
If both rules have the same specificity, the one later in the source file wins.

Fix a conflict without !important

If your stylesheet has a style conflict that is currently resolved by source order or a selector that is more specific than it needs to be, try refactoring it:

  1. Lower the specificity of the over-specific selector (e.g. remove unnecessary parent selectors)
  2. Make both selectors equal specificity and rely on deliberate source order
  3. Verify in DevTools that the intended rule still wins
💡 Need a hint?
A selector like nav ul li a.link can often be simplified to .site-nav__link — same element, lower specificity, clearer intent.
If you are using component-based class names from the BSB project, they should already be low specificity.

Trace an inherited value

Select a deeply nested element — a paragraph inside a section inside main. In the Computed tab of DevTools, find a property like color or font-family.

Click the arrow or expand the property to see where the value was inherited from. Trace the chain back to the element that set it.

💡 Need a hint?
Switch from the Styles tab to the Computed tab to see the final resolved value.
The Computed tab shows "Inherited from body" or similar labels when a value was not set directly.

You have finished when:

  • ☐ You found a conflicting property in the Styles panel
  • ☐ You calculated the specificity of both the winning and losing selectors
  • ☐ You resolved a conflict by adjusting specificity rather than adding !important
  • ☐ You traced an inherited value in the Computed tab back to its source

💪 Independent Challenge

Now try this on your own without hints!

Your Task:

Audit the specificity of a stylesheet. Open css/style.css from any project (the Black Swan Bistro project works well) and identify the three highest-specificity selectors. For each one, calculate its score and decide whether the specificity is necessary or could be reduced.
Requirements:
  • Calculate the three-column specificity score for at least five selectors in the stylesheet
  • Identify the three highest-specificity selectors and explain why they are high
  • Refactor at least one high-specificity selector to a lower-specificity alternative that still works
  • Verify the refactored rule still applies correctly using DevTools
Stretch Goals (Optional):
  • Check the entire stylesheet for any !important declarations and decide if each one is justified
  • Reorganise the stylesheet into clear layers: reset, layout, components, overrides
  • Add a comment at the top of the stylesheet documenting the intended specificity strategy

Success Criteria:

CriteriaYou've succeeded if...
Specificity awarenessScores are calculated correctly using the three-column method
Practical refactoringAt least one selector is simplified without changing the visual result
Tool useDevTools is used to verify that the refactored rule still applies correctly

Recap

The cascade is not a mystery — it is a decision tree. When two rules target the same property on the same element, the browser checks origin and importance first, then specificity, then source order. If no rule targets the property at all, inheritance may supply a value from a parent element.

Specificity is the layer that causes the most confusion. But once you can score selectors in three columns — IDs, classes, elements — you can predict which rule wins before you open the browser. And when something unexpected happens, DevTools shows you the browser's reasoning directly.

The best defence against style conflicts is not a clever trick. It is a consistent, low-specificity approach to writing selectors: flat class names, intentional source order, and no !important unless you genuinely need it.

You Can Read the Cascade Now

Key Takeaways:

  • The cascade resolves style conflicts in a clear order: origin and importance, then specificity, then source order.
  • Specificity is a three-column comparison (IDs, classes, elements) — not a single total.
  • Inheritance passes text-related properties down the DOM tree automatically. Layout properties do not inherit.
  • DevTools shows exactly which rule wins and which rules lose. Debugging starts there, not by guessing.
  • Keeping specificity low and consistent is the best way to avoid !important and make stylesheets maintainable.

Learning Objectives Review:

Look back at what you set out to learn. Can you now:

  • ✅ Explain how the cascade resolves conflicting CSS rules Check!
  • ✅ Calculate and compare specificity scores for different selectors Got it!
  • ✅ Predict which properties inherit and which do not Can explain it!
  • ✅ Use DevTools to identify and fix style conflicts Could teach this!
  • ✅ Apply low-specificity strategies to keep CSS maintainable Check!

If you can confidently answer "yes" to most of these, you're ready to move on!

Think & Reflect:

Cascade Thinking

  • Before this lesson, when a style did not apply, what was your first instinct? Has that changed?
  • Can you think of a time you used !important that could have been solved by adjusting specificity instead?

Debugging Confidence

  • Which DevTools feature feels most useful for CSS debugging now?
  • How would you explain a struck-through rule to someone who has never used DevTools?

Writing Better CSS

  • How does keeping specificity low help when multiple developers work on the same stylesheet?
  • What naming convention have you seen that naturally keeps specificity flat?

🎯 Looking Ahead:

Next, you will explore selectors, pseudo-elements, and motion — using the specificity awareness from this lesson to make confident styling decisions.

Recommended Next Steps

Continue Learning

Ready to move forward? Continue with the next tutorial in this series:

Styling Details: Selectors, Pseudo-elements, and Motion

Additional Resources

Deepen your understanding with these helpful resources:

  • MDN: Specificity - Clear explanation of how selectors compete and why some rules win.
  • MDN: Inheritance - Helpful for understanding which properties inherit and why some styles flow through the document.

Progress tracking is disabled. Enable it in to track your completed tutorials.