View All CSS Tutorials

CSS Cascade and Specificity Problems Guide

Learn how to debug CSS cascade and specificity problems, including selector conflicts, source order, inheritance, layers, media queries, and !important.

Infographic explaining how to debug CSS cascade and specificity problems, with examples of selectors, source order, inheritance, layers, and winning declarations.

Introduction

CSS can feel confusing when a style does not apply as expected.

You write a rule like this:

CSS
.title {  color: blue;}

But the page still shows the title in red.

Or you write:

CSS
.button {  background-color: green;}

But the button stays gray.

When this happens, CSS is not choosing styles randomly.

The browser is following the cascade.

The cascade is the system CSS uses to decide which declaration wins when more than one declaration could apply to the same element and property.

Specificity is one part of that system.

Source order is another part.

Inheritance, browser defaults, cascade layers, !important, media queries, and inline styles can also affect the result.

This guide explains common cascade and specificity problems and how to debug styles that do not apply as expected.

The Basic Problem

Suppose you have this HTML:

HTML
<p class=”intro”>Welcome to the page.</p>

And this CSS:

CSS
p {  color: black;} .intro {  color: navy;}

Both rules apply to the paragraph.

The p selector applies because the element is a paragraph.

The .intro selector applies because the paragraph has class="intro".

Both rules set the same property:

CSS
color

The browser must choose one value.

The paragraph becomes navy.

Why?

Because .intro is more specific than p.

This is a normal specificity conflict.

What the Cascade Decides

The cascade decides which CSS declaration wins.

A declaration is a property and value pair.

Example:

CSS
color: navy;

A rule can contain several declarations:

CSS
.card {  color: navy;  padding: 1rem;  background-color: white;}

The cascade works declaration by declaration.

It does not simply choose one whole rule and ignore the other whole rule.

Example:

CSS
.card {  color: black;  padding: 1rem;} .card {  color: blue;}

The final styles are:

CSS
color: blue;padding: 1rem;

The later rule overrides color.

The earlier rule still provides padding.

This is important when debugging.

A crossed-out declaration in developer tools does not mean the whole CSS rule failed.

It usually means that one declaration lost to another declaration.

A Simple Cascade Order

A simplified version of the cascade is:

TEXT
importancecascade layersspecificitysource order

There are more details in the full CSS cascade, but this simplified order explains many everyday problems.

In practical terms, ask:

TEXT
Is one declaration marked !important?Are cascade layers involved?Which selector is more specific?If specificity is equal, which declaration comes later?

You should also check:

TEXT
Does the selector actually match the element?Is the rule inside a media query that applies?Is the property inherited instead of directly applied?Is there an inline style?Is a browser default involved?

Problem 1: The Selector Does Not Match the Element

Before thinking about specificity, first check whether your selector matches the element at all.

HTML:

HTML
<p class=”intro”>Welcome.</p>

CSS:

CSS
.introduction {  color: blue;}

This rule does not apply.

Why?

The HTML has:

HTML
class=”intro”

But the CSS selects:

CSS
.introduction

Those are different class names.

The browser will not guess what you meant.

Debugging a Selector That Does Not Match

Check spelling carefully.

This does not match:

HTML
<div class=”card-title”>Title</div>
CSS
.card_title {  color: navy;}

The HTML uses a hyphen:

TEXT
card-title

The CSS uses an underscore:

TEXT
card_title

Those are different.

Also check whether you used the right selector type.

This selects a class:

CSS
.title {  color: navy;}

This selects an ID:

CSS
#title {  color: navy;}

This selects an element:

CSS
title {  color: navy;}

These are not the same.

Problem 2: The Selector Is Too Broad

Sometimes your selector works, but it affects more elements than expected.

Example:

CSS
.title {  color: blue;}

HTML:

HTML
<article class=”post”>  <h2 class=”title”>Post title</h2></article> <section class=”card”>  <h2 class=”title”>Card title</h2></section>

Both titles become blue.

If you only wanted card titles to change, the selector was too broad.

A more focused selector is:

CSS
.card .title {  color: blue;}

Or use a more specific class name:

CSS
.card-title {  color: blue;}

Problem 3: Another Rule Is More Specific

This is one of the most common problems.

CSS:

CSS
.title {  color: blue;} .card .title {  color: red;}

HTML:

HTML
<div class=”card”>  <h2 class=”title”>Card title</h2></div>

The title becomes red.

Why?

Both selectors match the heading.

But .card .title is more specific than .title.

The selector .card .title contains two class selectors.

The selector .title contains one class selector.

So .card .title wins.

Specificity Basics

A simplified specificity order is:

TEXT
type selectorclass selectorID selectorinline style

From weaker to stronger:

CSS
p {  color: black;}
CSS
.intro {  color: navy;}
CSS
#main-intro {  color: green;}
HTML
<p style=”color: red;”>Text</p>

In general:

TEXT
class selectors beat type selectorsID selectors beat class selectorsinline styles beat normal stylesheet rules

This is simplified, but it is useful for most beginner CSS problems.

Specificity Score

Specificity is often described with a score.

A common format is:

TEXT
IDs – classes/attributes/pseudo-classes – types/pseudo-elements

Example:

CSS
p

Specificity:

TEXT
0-0-1

Example:

CSS
.intro

Specificity:

TEXT
0-1-0

Example:

CSS
#main-title

Specificity:

TEXT
1-0-0

Example:

CSS
.card p

Specificity:

TEXT
0-1-1

Example:

CSS
#main-content .card p

Specificity:

TEXT
1-1-1

The leftmost column matters most.

An ID selector usually beats many class or type selectors because the ID column is stronger.

Problem 4: You Expected Source Order to Win, but Specificity Won

Source order means later rules can win when specificity is equal.

But source order does not automatically beat specificity.

Example:

CSS
.intro {  color: navy;} p {  color: black;}

HTML:

HTML
<p class=”intro”>Welcome.</p>

The paragraph becomes navy.

Why?

The p rule comes later.

But .intro is more specific.

A later weaker rule does not beat an earlier stronger rule.

When Source Order Wins

Source order wins when competing declarations have equal specificity.

Example:

CSS
.message {  color: green;} .message {  color: blue;}

HTML:

HTML
<p class=”message”>Saved.</p>

The paragraph becomes blue.

Both selectors are the same.

Both set color.

The later declaration wins.

Another example:

CSS
.success {  color: green;} .warning {  color: orange;}

HTML:

HTML
<p class=”success warning”>Check your email.</p>

The paragraph becomes orange.

Both .success and .warning have equal specificity.

Both apply to the paragraph.

.warning appears later in the CSS.

So .warning wins.

Problem 5: The HTML Class Order Does Not Control the Winner

This is a common beginner mistake.

HTML:

HTML
<p class=”success warning”>Message</p>

Some people expect .warning to win because it appears last in the HTML class attribute.

But class order in HTML does not decide the winner.

CSS order decides.

Example:

CSS
.warning {  color: orange;} .success {  color: green;}

HTML:

HTML
<p class=”success warning”>Message</p>

The paragraph becomes green.

Why?

The .success rule appears later in the CSS.

This version gives the same result:

HTML
<p class=”warning success”>Message</p>

The class order in HTML does not matter for this conflict.

The CSS rule order matters.

Problem 6: Inline Styles Are Winning

Inline styles are written directly on an HTML element.

Example:

HTML
<p class=”intro” style=”color: red;”>  Welcome.</p>

CSS:

CSS
.intro {  color: blue;}

The paragraph becomes red.

Why?

The inline style is stronger than the normal class rule.

This is why inline styles can be difficult to override.

They are useful in rare cases, but they often make CSS harder to maintain.

Overriding Inline Styles

A normal stylesheet rule usually does not override an inline style:

CSS
.intro {  color: blue;}

But an important stylesheet rule can override a normal inline style:

CSS
.intro {  color: blue !important;}

HTML:

HTML
<p class=”intro” style=”color: red;”>  Welcome.</p>

The paragraph becomes blue.

Use this carefully.

If you control the HTML, it is usually better to remove the inline style.

Problem 7: !important Is Blocking Your Rule

The !important flag makes a declaration harder to override.

Example:

CSS
.alert {  color: red !important;} .alert {  color: black;}

HTML:

HTML
<p class=”alert”>Important message.</p>

The paragraph stays red.

The later black declaration is normal.

The earlier red declaration is important.

Important declarations beat normal declarations.

Important Does Not Always Win Against Important

If both declarations are important, specificity still matters.

Example:

CSS
.alert {  color: red !important;} .warning.alert {  color: orange !important;}

HTML:

HTML
<p class=”alert warning”>Warning message.</p>

The paragraph becomes orange.

Both declarations are important.

.warning.alert is more specific than .alert.

So orange wins.

If specificity is equal, source order decides between important declarations:

CSS
.alert {  color: red !important;} .alert {  color: orange !important;}

The paragraph becomes orange.

Debugging !important

When a style does not apply, check developer tools for !important.

If the winning declaration has !important, your normal rule will not beat it.

You have several options:

TEXT
remove the unnecessary !importantuse a better cascade structureuse a more specific important rulemove the rule into a stronger cascade layer if layers are involvedavoid fighting the rule if it is a deliberate utility or state class

Do not automatically add more !important.

That can create a chain where every future override also needs !important.

Problem 8: The Property Is Inherited, Not Directly Applied

Inheritance can make debugging confusing.

Example:

CSS
.card {  color: green;} p {  color: blue;}

HTML:

HTML
<div class=”card”>  <p>This paragraph is blue.</p></div>

The paragraph is blue.

Why?

The .card rule sets the colour on the parent.

The paragraph could inherit green from the parent.

But the paragraph also has a direct rule:

CSS
p {  color: blue;}

A direct value on the element beats an inherited value from the parent.

Parent Specificity Does Not Transfer to Children

This surprises many people.

CSS:

CSS
#main {  color: green;} p {  color: blue;}

HTML:

HTML
<div id=”main”>  <p>This paragraph is blue.</p></div>

The paragraph becomes blue.

The #main selector is more specific than p.

But #main targets the parent <div>, not the paragraph.

The p selector targets the paragraph directly.

The paragraph receives:

TEXT
inherited color: greendirect color: blue

The direct colour wins.

Specificity from the parent does not transfer to inherited child values.

Problem 9: You Expected a Property to Inherit, but It Does Not

Not every CSS property inherits.

Text-related properties often inherit:

TEXT
colorfont-familyfont-sizefont-stylefont-weightline-heightletter-spacingtext-aligntext-transformvisibility

Box and layout properties usually do not inherit:

TEXT
marginpaddingborderbackgroundwidthheightdisplaypositiongapgrid-template-columnsflex-direction

Example:

CSS
.card {  padding: 2rem;}

HTML:

HTML
<div class=”card”>  <p>Card text.</p></div>

The paragraph does not inherit padding: 2rem.

The card has padding.

The paragraph sits inside the padded card.

If you want the paragraph itself to have padding, you must style the paragraph directly:

CSS
.card p {  padding: 2rem;}

Problem 10: Browser Defaults Are Applying

Browsers have default styles.

For example, browsers usually give headings a large bold font, paragraphs margins, links blue underlined styling, and lists indentation.

Example:

HTML
<h1>Page title</h1><p>Paragraph text.</p><a href=”/about”>About</a>

Even with no CSS, these elements have visible styles.

Those styles come from the browser’s user agent stylesheet.

This can confuse debugging because a style may appear even though you did not write it.

Browser Default Example

CSS:

CSS
body {  font-size: 18px;}

HTML:

HTML
<h1>Page title</h1><p>Paragraph text.</p>

The paragraph may use 18px.

The heading may still appear much larger.

Why?

The heading has browser default styles.

The browser likely gives h1 a direct font size.

That direct browser rule can override inherited font-size from body.

If you want control, write a direct heading rule:

CSS
h1 {  font-size: 2rem;}

Problem 11: A Reset Removed Default Styles

Many projects use a reset.

Example:

CSS
h1,h2,p,ul {  margin: 0;}

This removes default margins from headings, paragraphs, and lists.

If you expect paragraphs to have spacing, but they do not, a reset may be responsible.

Add your own spacing:

CSS
p {  margin-bottom: 1rem;}

Or for article content:

CSS
.article p {  margin-block: 1rem;}

A reset is not a bug.

It just means you are responsible for adding the styles you want.

Problem 12: A Shorthand Property Overrode a Longhand Property

Shorthand properties can override multiple related longhand properties.

Example:

CSS
.box {  margin-top: 2rem;} .box {  margin: 0;}

The final top margin is 0.

Why?

The shorthand property margin sets all four sides:

TEXT
margin-topmargin-rightmargin-bottommargin-left

So margin: 0; overrides the earlier margin-top: 2rem;.

Now reverse the order:

CSS
.box {  margin: 0;} .box {  margin-top: 2rem;}

The final top margin is 2rem.

The later longhand overrides one part of the earlier shorthand.

Shorthand Border Example

CSS:

CSS
.card {  border-color: blue;} .card {  border: 1px solid gray;}

The final border colour is gray.

Why?

The border shorthand sets:

TEXT
border-widthborder-styleborder-color

Because it comes later, it overrides the earlier border-color.

Reverse the order:

CSS
.card {  border: 1px solid gray;} .card {  border-color: blue;}

Now the border colour is blue.

When debugging, look for shorthand properties that silently reset longhand values.

Problem 13: The Rule Is Inside a Media Query That Does Not Apply

Media queries apply only when their condition is true.

Example:

CSS
.card {  width: 100%;} @media (min-width: 900px) {  .card {    width: 50%;  }}

If the viewport is 700px wide, the media query does not apply.

The card remains 100%.

If the viewport is 950px wide, the media query applies.

The card becomes 50%.

When a style does not apply, check whether it is inside a media query.

Media Query Order Problems

Multiple media queries can apply at the same time.

Example:

CSS
.box {  width: 100%;} @media (min-width: 900px) {  .box {    width: 50%;  }} @media (min-width: 600px) {  .box {    width: 75%;  }}

At 950px, both media queries apply.

The 900px rule says 50%.

The 600px rule says 75%.

The 600px rule comes later.

If specificity is equal, the later rule wins.

The box becomes 75%.

That may be unexpected.

A common mobile-first order is:

CSS
.box {  width: 100%;} @media (min-width: 600px) {  .box {    width: 75%;  }} @media (min-width: 900px) {  .box {    width: 50%;  }}

Now larger breakpoint rules come later.

Problem 14: The File Order Is Wrong

Source order also applies across CSS files.

HTML:

HTML
<link rel=”stylesheet” href=”theme.css”><link rel=”stylesheet” href=”base.css”>

If both files style the same thing, base.css can override theme.css because it is loaded later.

Example:

theme.css:

CSS
button {  background-color: blue;}

base.css:

CSS
button {  background-color: gray;}

The button becomes gray.

If you expected the theme to win, load it later:

HTML
<link rel=”stylesheet” href=”base.css”><link rel=”stylesheet” href=”theme.css”>

Problem 15: A Cascade Layer Is Winning

Cascade layers are created with @layer.

Example:

CSS
@layer base, components, utilities; @layer base {  .title {    color: black;  }} @layer utilities {  .text-blue {    color: blue;  }}

HTML:

HTML
<h2 class=”title text-blue”>Heading</h2>

The heading becomes blue.

The utilities layer comes after the base layer.

For normal declarations, later layers have higher priority.

Layer order is considered before specificity.

Layer Order Can Beat Specificity

Example:

CSS
@layer base, utilities; @layer base {  #main .card .title {    color: red;  }} @layer utilities {  .text-blue {    color: blue;  }}

HTML:

HTML
<div id=”main”>  <div class=”card”>    <h2 class=”title text-blue”>Title</h2>  </div></div>

The title becomes blue.

Why?

The base selector is more specific.

But utilities is a later layer.

For normal declarations, later layers beat earlier layers before specificity is compared.

If layers are involved, check the layer order before blaming specificity.

Problem 16: Unlayered CSS Is Overriding Layered CSS

This can surprise people.

CSS:

CSS
@layer components {  .card p {    color: navy;  }} p {  color: black;}

HTML:

HTML
<div class=”card”>  <p>This paragraph is black.</p></div>

The paragraph becomes black.

Why?

The p rule is unlayered.

For normal author styles, unlayered CSS has higher priority than layered CSS.

If you are using cascade layers, be careful with unlayered rules.

A better version may be:

CSS
@layer base {  p {    color: black;  }} @layer components {  .card p {    color: navy;  }}

Now the component layer can override the base layer.

Problem 17: The Element Is Outside the Scope

If you use @scope, selectors inside the scope only apply within the scope root.

Example:

CSS
@scope (.card) {  .title {    color: navy;  }}

HTML:

HTML
<div class=”card”>  <h2 class=”title”>Card title</h2></div> <div class=”modal”>  <h2 class=”title”>Modal title</h2></div>

Only the card title becomes navy.

The modal title is outside .card.

So the scoped rule does not apply.

When debugging scoped CSS, check whether the element is actually inside the scope root.

Problem 18: A Scope Limit Excludes the Element

@scope can have a lower boundary.

Example:

CSS
@scope (.article) to (.callout) {  p {    color: #333333;  }}

HTML:

HTML
<article class=”article”>  <p>Main paragraph.</p>   <aside class=”callout”>    <p>Callout paragraph.</p>  </aside></article>

The main paragraph is styled.

The callout paragraph is not styled by this scoped rule.

Why?

.callout is the scope limit.

The scoped rule stops before entering it.

If a scoped rule does not apply, check whether a scope limit excludes the element.

Problem 19: The Pseudo-Class State Is Not Active

Pseudo-classes apply only in certain states.

Example:

CSS
button:hover {  background-color: blue;}

This rule applies when the button is hovered.

If the button is not hovered, the rule does not apply.

Another example:

CSS
input:focus {  border-color: blue;}

This applies when the input has focus.

If the input is not focused, the rule does not apply.

When debugging pseudo-classes, check whether the state is active.

Browser developer tools can often force states like :hover, :focus, and :active.

Links have several states:

CSS
a:linka:visiteda:hovera:focusa:active

Some states can overlap.

Example:

CSS
a:hover {  color: red;} a:focus {  color: blue;}

If a link is both hovered and focused, both rules may apply.

If specificity is equal, the later rule wins.

So the link may become blue.

A clear pattern is:

CSS
a:link {  color: blue;} a:visited {  color: purple;} a:hover,a:focus-visible {  text-decoration: underline;} a:active {  color: red;}

Think about overlapping states when debugging link styles.

Problem 21: A Pseudo-Element Is Not Being Created

Pseudo-elements like ::before and ::after often need content.

Example:

CSS
.note::before {  content: “Note: “;  font-weight: bold;}

If you forget content, the pseudo-element may not appear:

CSS
.note::before {  font-weight: bold;}

The rule exists, but there may be no generated content to show.

Add content:

CSS
.note::before {  content: “Note: “;}

Also check whether the selected element can use the pseudo-element as expected.

Problem 22: The Property Does Not Apply to That Element

Some properties only work in certain contexts.

Example:

CSS
.item {  justify-self: center;}

This works on grid items.

But if the parent is not a grid container, the result may not be what you expect.

Example:

CSS
.parent {  display: block;} .item {  justify-self: center;}

The justify-self declaration may not affect layout as expected.

Another example:

CSS
span {  width: 200px;}

A normal inline <span> does not behave like a block box.

The width may not apply as expected.

You may need:

CSS
span {  display: inline-block;  width: 200px;}

When a declaration appears active but has no visible effect, check whether the property applies in that layout context.

Problem 23: The Parent Layout Controls the Child

Some styles affect children without being inherited.

Example:

CSS
.container {  display: flex;}

HTML:

HTML
<div class=”container”>  <div class=”item”>One</div>  <div class=”item”>Two</div></div>

The children become flex items.

But they do not inherit display: flex.

The parent creates a flex formatting context.

The children participate in that layout.

If child layout is not behaving as expected, inspect the parent.

Look for:

TEXT
display: flexdisplay: gridpositionoverflowalign-itemsjustify-contentgrid-template-columnsgap

The issue may be on the parent, not the child.

Problem 24: display: none on a Parent Hides Everything

If a parent has display: none, its children are not displayed.

Example:

CSS
.modal {  display: none;} .modal .message {  display: block;}

HTML:

HTML
<div class=”modal”>  <p class=”message”>Hello.</p></div>

The message does not appear.

Why?

The parent .modal is not displayed.

Setting the child to display: block does not make it visible while the parent is display: none.

You must display the parent:

CSS
.modal.is-open {  display: block;}

Problem 25: opacity on a Parent Affects Children Visually

The opacity property does not inherit in the normal way.

But it affects the rendered element and its contents.

Example:

CSS
.card {  opacity: 0.5;} .card p {  opacity: 1;}

HTML:

HTML
<div class=”card”>  <p>Card text.</p></div>

The paragraph still appears faded.

Why?

The whole card is rendered at 0.5 opacity.

The child cannot escape the parent’s rendered opacity.

If you only want a faded background, use a background colour with alpha instead:

CSS
.card {  background-color: rgb(0 0 0 / 0.5);}

instead of fading the whole card.

Problem 26: z-index Does Not Work as Expected

z-index only works in certain positioning and stacking contexts.

Example:

CSS
.box {  z-index: 10;}

If .box is not positioned or not in a relevant stacking context, z-index may not behave as expected.

Often you need:

CSS
.box {  position: relative;  z-index: 10;}

But stacking contexts can also be created by properties such as:

TEXT
position with z-indexopacity less than 1transformfilterisolationcertain contain values

If z-index is not working, inspect the stacking context.

The problem may not be the number.

A z-index: 9999 inside a lower stacking context can still appear behind something in a higher stacking context.

Problem 27: A Custom Property Is Not Defined Where You Use It

CSS custom properties inherit.

Example:

CSS
.card {brand-color: blue;} .card h2 {  color: var(–brand-color);}

This works because --brand-color is defined on .card and inherited by the heading.

But this may fail:

CSS
.card h2 {  color: var(–brand-color);}

If --brand-color is not defined anywhere in the heading’s inheritance path, the property may become invalid.

Use a fallback:

CSS
.card h2 {  color: var(–brand-color, navy);}

Now the heading uses navy if --brand-color is missing.

Problem 28: A Custom Property Value Is Invalid

Custom properties are substituted later.

Example:

CSS
:root {spacing: blue;} .card {  padding: var(–spacing);}

This does not make sense.

padding needs a length or valid spacing value.

blue is not valid for padding.

The declaration becomes invalid when used.

Use valid values:

CSS
:root {spacing: 1rem;} .card {  padding: var(–spacing);}

If a custom property-based style does not apply, check both where the variable is defined and whether the value is valid for the property.

Problem 29: The CSS Has a Syntax Error

A missing brace or semicolon can make later CSS behave strangely.

Example:

CSS
.card {  color: navy;  padding: 1rem; .button {  background-color: blue;}

The .card rule is missing a closing brace:

CSS
}

The browser may parse the following CSS differently than you expect.

Correct:

CSS
.card {  color: navy;  padding: 1rem;} .button {  background-color: blue;}

When styles stop applying after a certain point in a file, check for syntax errors above that point.

Problem 30: Invalid Values Are Ignored

If a property has an invalid value, the browser ignores that declaration.

Example:

CSS
.card {  color: 20px;}

20px is not a valid value for color.

The declaration is ignored.

Another example:

CSS
.box {  display: centre;}

The valid value is not centre.

The browser ignores the invalid declaration.

Correct:

CSS
.box {  text-align: center;}

or:

CSS
.box {  display: flex;  justify-content: center;}

depending on what you are trying to do.

Problem 31: The Stylesheet Is Not Loading

Sometimes the CSS rule is fine, but the file is not loaded.

HTML:

HTML
<link rel=”stylesheet” href=”style.css”>

Check:

TEXT
Is the file path correct?Is the file name correct?Is the file extension correct?Is the server returning the file?Is the CSS cached?Is the link tag in the document?

If the file is named:

TEXT
styles.css

but the HTML links to:

TEXT
style.css

the stylesheet will not load.

Use browser developer tools to check the Network panel if needed.

Problem 32: Browser Cache Is Showing Old CSS

Sometimes you change CSS, but the browser still shows an old version.

Try:

TEXT
hard refreshdisable cache in developer toolscheck whether your build process generated the updated filecheck whether the server or CDN is caching the CSS

This is especially common when working with bundled files or deployed sites.

If developer tools show the old CSS, your latest changes are not reaching the browser.

Problem 33: The Build Tool Generated Different CSS

If you use Sass, PostCSS, Tailwind, CSS Modules, a framework, or a bundler, the CSS you write may not be exactly what the browser receives.

Example:

CSS
.card {  &__title {    color: navy;  }}

This may compile to:

CSS
.card__title {  color: navy;}

If your HTML uses:

HTML
<h2 class=”card-title”>Title</h2>

the rule does not match.

The CSS expects:

HTML
<h2 class=”card__title”>Title</h2>

When using tools, inspect the final CSS in the browser, not only the source file.

Problem 34: CSS Modules Changed the Class Name

CSS Modules may transform class names.

You may write:

CSS
.title {  color: navy;}

But the final class may become something like:

TEXT
Title_title__abc123

If you manually write:

HTML
<h2 class=”title”>Title</h2>

the generated CSS may not match.

With CSS Modules, classes usually need to be applied through the imported styles object in your component code.

This is not a cascade problem.

It is a class name generation problem.

Problem 35: Framework Styles Are Overriding Your CSS

Frameworks and component libraries often ship their own CSS.

Example:

CSS
.btn {  background-color: gray;}

Your CSS:

CSS
.button {  background-color: blue;}

HTML:

HTML
<button class=”btn button”>Save</button>

If the framework CSS loads later, or if its selector is more specific, it may win.

Options:

TEXT
load your CSS after the framework CSSuse a clearer component selectoruse cascade layersuse framework theming optionsavoid fighting the framework when configuration is availableuse !important only when necessary

A Debugging Workflow

When a style does not apply, use this workflow.

First, inspect the element in browser developer tools.

Second, check whether your selector appears in the Styles panel.

Third, check whether the declaration is crossed out.

Fourth, find the winning declaration.

Fifth, compare the two declarations.

Ask:

TEXT
Does my selector match?Is my declaration invalid?Is another declaration setting the same property?Is the other declaration important?Are cascade layers involved?Is the other selector more specific?Does the other rule come later?Is the value inherited?Is a media query involved?Is an inline style involved?

This process is more reliable than guessing.

Step 1: Check Whether the Rule Exists

If your rule does not appear in developer tools at all, possible causes include:

TEXT
the selector does not matchthe CSS file is not loadedthe CSS is inside a media query that does not applythe CSS is inside an unsupported feature querythere is a syntax erroryour build tool did not output the rule

Example:

CSS
@media (min-width: 900px) {  .card {    padding: 2rem;  }}

At 700px, this rule may not appear as active because the media condition is false.

Step 2: Check Whether the Declaration Is Crossed Out

If a declaration is crossed out, it matched but lost.

Example:

CSS
.card {  color: black;} .card.featured {  color: blue;}

HTML:

HTML
<div class=”card featured”>Text</div>

In developer tools, color: black; may be crossed out.

That means another declaration wins for the same property.

Find the winning declaration and compare:

TEXT
importancelayerspecificitysource order

Step 3: Check Computed Styles

The Computed panel shows the final value the browser uses.

For example, the Styles panel might show many possible color declarations.

The Computed panel shows the final color.

This helps you answer:

TEXT
What value is actually being used?Which rule provided that value?Is the value inherited?Is the value coming from a browser default?

Computed styles are especially useful for inherited properties and layout properties.

Step 4: Disable Rules Temporarily

In developer tools, you can usually uncheck declarations.

This helps you see what changes.

For example, if this declaration is winning:

CSS
.card .title {  color: red;}

Uncheck it.

If your blue style appears, you know the red declaration was blocking it.

This is a fast way to identify conflicts.

Step 5: Test a Simpler Selector

If your selector is complex, test a simpler one.

Original:

CSS
main .content .article .card .title {  color: blue;}

Test:

CSS
.title {  outline: 2px solid red;}

If the outline appears, the element matches .title.

Then the issue may be specificity, source order, or the original selector path.

If the outline does not appear, maybe the class name is wrong or the CSS is not loaded.

Step 6: Avoid Random !important

Do not immediately fix this:

CSS
.title {  color: blue;}

by writing this:

CSS
.title {  color: blue !important;}

First, find out why the original rule lost.

Maybe the winning rule is:

CSS
.card .title {  color: red;}

Then a cleaner fix may be:

CSS
.card .title {  color: blue;}

or:

CSS
.card-title {  color: blue;}

or reorganising source order.

Use !important only when it is intentional.

Example Debugging Session: Colour Does Not Apply

HTML:

HTML
<div class=”card”>  <h2 class=”title”>Card title</h2></div>

CSS:

CSS
.title {  color: blue;} .card .title {  color: red;}

Problem:

TEXT
The title is red, but you expected blue.

Debug:

TEXT
Both selectors match.Both set color.Neither uses !important.No layers are involved..card .title has higher specificity than .title.Therefore red wins.

Fix options:

CSS
.card .title {  color: blue;}

or:

CSS
.card-title {  color: blue;}

or remove the competing red rule if it is not needed.

Example Debugging Session: Button Background Does Not Apply

HTML:

HTML
<button class=”button primary”>Save</button>

CSS:

CSS
.primary {  background-color: blue;} .button {  background-color: gray;}

Problem:

TEXT
The button is gray, but you expected blue.

Debug:

TEXT
Both .primary and .button match.Both are class selectors.Specificity is equal..button appears later.Therefore gray wins.

Fix:

CSS
.button {  background-color: gray;} .primary {  background-color: blue;}

or use a clearer modifier:

CSS
.button {  background-color: gray;} .button-primary {  background-color: blue;}

HTML:

HTML
<button class=”button button-primary”>Save</button>

Example Debugging Session: Card Text Inherits Unexpected Colour

HTML:

HTML
<section class=”warning”>  <div class=”card”>    <p>Card text.</p>  </div></section>

CSS:

CSS
.warning {  color: red;} .card {  background-color: white;}

Problem:

TEXT
The card text is red, but you expected normal text.

Debug:

TEXT
color inherits..warning sets color on the parent section..card does not set color.p does not set color.The paragraph inherits red through its ancestors.

Fix:

CSS
.card {  background-color: white;  color: #222222;}

This gives the card its own text colour.

HTML:

HTML
<p class=”notice”>  Read the <a href=”/guide”>guide</a>.</p>

CSS:

CSS
.notice {  color: green;}

Problem:

TEXT
The paragraph is green, but the link is blue.

Debug:

TEXT
color normally inherits.But browsers commonly style links directly.The link has browser default link colour.A direct link rule can override inherited parent colour.

Fix:

CSS
.notice a {  color: inherit;}

Now the link uses the notice colour.

Example Debugging Session: Utility Class Does Not Work

HTML:

HTML
<div class=”card text-center”>  Card content</div>

CSS:

CSS
.text-center {  text-align: center;} .card {  text-align: left;}

Problem:

TEXT
The text is left-aligned, but you expected centered.

Debug:

TEXT
Both selectors are class selectors.Specificity is equal..card appears later.Therefore text-align: left wins.

Fix:

CSS
.card {  text-align: left;} .text-center {  text-align: center;}

Or, if using layers:

CSS
@layer components, utilities; @layer components {  .card {    text-align: left;  }} @layer utilities {  .text-center {    text-align: center;  }}

Example Debugging Session: Layered Utility Does Not Work

HTML:

HTML
<div class=”card text-red”>  Card content</div>

CSS:

CSS
@layer utilities {  .text-red {    color: red;  }} @layer components {  .card {    color: navy;  }}

Problem:

TEXT
The text is navy, but you expected red.

Debug:

TEXT
No layer order was declared up front.utilities layer was created first.components layer was created second.For normal declarations, later layers have higher priority.components wins.

Fix:

CSS
@layer components, utilities; @layer utilities {  .text-red {    color: red;  }} @layer components {  .card {    color: navy;  }}

Now the declared layer order gives utilities higher priority.

Example Debugging Session: Media Query Override Does Not Work

CSS:

CSS
.card {  padding: 1rem;} @media (min-width: 900px) {  .card {    padding: 2rem;  }}

Problem:

TEXT
The card still has 1rem padding.

Debug:

TEXT
Check viewport width.If viewport is below 900px, the media query does not apply.

If the viewport is above 900px, check for another later rule:

CSS
.card {  padding: 1rem;} @media (min-width: 900px) {  .card {    padding: 2rem;  }} .card.featured {  padding: 3rem;}

If the card has class featured, .card.featured is more specific and appears later.

So 3rem wins.

Example Debugging Session: Width Does Not Apply to Span

HTML:

HTML
<span class=”badge”>New</span>

CSS:

CSS
.badge {  width: 100px;  background-color: yellow;}

Problem:

TEXT
The width does not seem to work.

Debug:

TEXT
span is inline by default.Width does not affect normal inline elements the way it affects block or inline-block elements.

Fix:

CSS
.badge {  display: inline-block;  width: 100px;  background-color: yellow;}

The issue was not specificity.

The property did not apply as expected because of the element’s display type.

Common Fixes

Common cascade and specificity fixes include:

TEXT
correct the selectormove the rule laterreduce the specificity of the competing ruleincrease specificity deliberatelyremove unnecessary !importantuse a modifier classuse cascade layersput utilities in a higher-priority layermove broad styles into a lower-priority layerwrite direct styles on the elementset inherited properties on the right parentremove inline styles when possible

Choose the fix based on the actual cause.

Do not use the same fix for every problem.

Fix 1: Correct the Selector

If the selector does not match, fix the selector or the HTML.

Wrong:

HTML
<h2 class=”card-title”>Title</h2>
CSS
.card_title {  color: navy;}

Correct:

CSS
.card-title {  color: navy;}

Or change the HTML if the CSS class is the one you want.

Fix 2: Move the Rule Later

If specificity is equal, source order can solve the problem.

Before:

CSS
.primary {  background-color: blue;} .button {  background-color: gray;}

After:

CSS
.button {  background-color: gray;} .primary {  background-color: blue;}

Use this when the rule order is the actual problem.

Fix 3: Use a More Specific Selector

Sometimes you need a selector that describes the target more clearly.

Before:

CSS
.title {  color: blue;}

Competing rule:

CSS
.card .title {  color: red;}

Fix:

CSS
.card .title {  color: blue;}

Use specificity deliberately.

Do not create unnecessarily long selectors.

Fix 4: Use a Modifier Class

Instead of fighting base styles, use a modifier.

HTML:

HTML
<button class=”button button-primary”>Save</button>

CSS:

CSS
.button {  background-color: gray;  color: white;} .button-primary {  background-color: blue;}

This is clearer than writing random overrides later.

Modifier classes are useful for component variations.

Fix 5: Reduce Specificity

If a selector is too specific, it becomes hard to override.

Hard to override:

CSS
.page .main .content .card .title {  color: navy;}

Better:

CSS
.card-title {  color: navy;}

Lower specificity makes CSS easier to maintain.

Use structure, naming, layers, and source order instead of long selector chains.

Fix 6: Use Cascade Layers

Cascade layers help organise priority.

Example:

CSS
@layer reset, base, components, utilities; @layer components {  .card {    color: navy;  }} @layer utilities {  .text-red {    color: red;  }}

HTML:

HTML
<div class=”card text-red”>  Text</div>

The text is red because the utilities layer has higher priority.

This can reduce the need for !important.

Fix 7: Remove Unnecessary !important

Before:

CSS
.button {  background-color: gray !important;} .button-primary {  background-color: blue;}

The primary button stays gray.

Better:

CSS
.button {  background-color: gray;} .button-primary {  background-color: blue;}

Now the modifier can work normally.

Avoid placing !important in base component styles unless there is a strong reason.

Fix 8: Use inherit Intentionally

If a child should match its parent, use inherit.

Example:

CSS
.card {  color: navy;} .card a {  color: inherit;}

Now links inside the card use the card’s text colour.

This is useful for links, buttons, form controls, and icons.

Fix 9: Use Direct Styling Instead of Relying on Inheritance

If a child should not inherit a parent style, give it its own value.

Example:

CSS
.warning {  color: red;} .card {  color: #222222;}

HTML:

HTML
<section class=”warning”>  <div class=”card”>    <p>Normal card text.</p>  </div></section>

The card text can now use its own colour instead of inheriting warning red.

Fix 10: Check Layout Context

If a property has no visible effect, check the layout context.

Example:

CSS
.item {  align-self: center;}

This needs the item to be inside a flex or grid container.

Parent:

CSS
.container {  display: flex;}

Without the correct parent layout, the property may not work as expected.

A Practical Debugging Checklist

Use this checklist when styles do not apply.

TEXT
1. Is the stylesheet loaded?2. Does the selector match the element?3. Is the declaration valid?4. Is the rule inside a media query that applies?5. Is the rule inside @supports that applies?6. Is the rule inside @scope, and is the element inside the scope?7. Is the property being set somewhere else?8. Is the other declaration !important?9. Are cascade layers involved?10. Is one rule unlayered?11. Which selector is more specific?12. If specificity is equal, which rule comes later?13. Is an inline style involved?14. Is the value inherited from a parent?15. Is a browser default involved?16. Is a reset involved?17. Is a shorthand overriding a longhand?18. Does the property apply to this element or layout context?19. Is a parent controlling the layout?20. Is a build tool or framework changing the final CSS?

Work through the list in order.

Most CSS problems become clear once you identify the winning declaration.

Developer Tools Are Essential

Browser developer tools are the best way to debug cascade and specificity problems.

Use them to inspect the element.

Look for:

TEXT
matched CSS rulescrossed-out declarationsinherited stylescomputed final valuesmedia query conditionscascade layersbrowser default stylesinline styles

The browser already knows why a rule won.

Developer tools help you see that decision.

What Crossed-Out CSS Means

A crossed-out declaration means:

TEXT
This declaration matched, but another declaration won.

Example:

CSS
.title {  color: blue;} .card .title {  color: red;}

If the element is inside .card, developer tools may cross out:

CSS
color: blue;

because:

CSS
color: red;

wins.

Crossed-out CSS is not always an error.

It is often just the cascade working.

What Missing CSS Means

If your rule does not appear at all in developer tools, possible causes include:

TEXT
the selector does not matchthe CSS file did not loadthe CSS is not included in the final buildthe media query is inactivethe feature query is inactivethe browser does not support the syntaxthere is a syntax error

This is different from crossed-out CSS.

Crossed-out means the rule matched but lost.

Missing means the rule may not be active or may not match.

Use Outlines for Debugging

A useful debugging trick is adding an outline:

CSS
.card {  outline: 2px solid red;}

Outlines do not affect layout like borders do.

They are useful for checking whether a selector matches.

If the outline appears, your selector matches the element.

If the outline does not appear, the selector may be wrong or the CSS may not be loaded.

Remove debugging outlines after you finish.

Avoid Specificity Battles

A specificity battle happens when each new rule becomes more specific than the last.

Example:

CSS
.card .title {  color: navy;} .page .card .title {  color: green;} body .page .card .title {  color: red;}

This gets harder to maintain.

Better:

CSS
.card-title {  color: navy;} .card-title-success {  color: green;} .card-title-error {  color: red;}

Or use layers and modifiers.

Good CSS should be predictable.

It should not require increasingly long selectors.

Avoid Overusing IDs in CSS

ID selectors are very specific.

Example:

CSS
#main-title {  color: navy;}

This can be hard to override later with classes.

Example:

CSS
.title {  color: green;}

The ID selector wins.

For reusable styling, classes are usually easier to manage:

CSS
.page-title {  color: navy;}

Use IDs when they are appropriate, but do not rely on them for everyday component styling.

Avoid Random Overrides

This is hard to debug:

CSS
.card {  padding: 1rem;} .button {  background-color: blue;} .card {  padding: 2rem;} .title {  color: navy;} .card {  padding: 0.5rem;}

The final .card padding is 0.5rem, but you have to scan the whole stylesheet to know that.

Better:

CSS
.card {  padding: 0.5rem;} .button {  background-color: blue;} .title {  color: navy;}

Keep related styles together when possible.

Organise CSS from General to Specific

A common organisation pattern is:

TEXT
resetbaselayoutcomponentsutilitiesoverrides

Example:

CSS
/* reset */* {  box-sizing: border-box;} /* base */body {  font-family: Arial, sans-serif;} /* component */.button {  padding: 0.75rem 1rem;} /* utility */.text-center {  text-align: center;}

This makes the cascade more predictable.

Cascade layers can make this structure even clearer:

CSS
@layer reset, base, layout, components, utilities;

Keep Specificity Low by Default

Low specificity is easier to override.

Good:

CSS
.card {  padding: 1rem;}

Harder to override:

CSS
main .content .card {  padding: 1rem;}

Even harder:

CSS
body main .content section.card[data-featured=”true”] {  padding: 1rem;}

Use the simplest selector that accurately targets the element.

Prefer Classes for Components

Classes are reusable and predictable.

Example:

CSS
.card {  padding: 1rem;} .card-title {  color: navy;} .card-text {  color: #333333;}

This is usually easier to manage than deeply nested selectors:

CSS
main section article div h2 {  color: navy;}

Class-based component styling makes debugging easier.

Use Utility Classes Deliberately

Utility classes are small classes that do one job.

Example:

CSS
.text-center {  text-align: center;} .mt-0 {  margin-top: 0;}

If utilities should override components, place them later or in a higher-priority layer:

CSS
@layer components, utilities;

Then:

CSS
@layer utilities {  .text-center {    text-align: center;  }}

This avoids unnecessary !important.

Use Comments for Intentional Overrides

If an override exists for a special reason, explain it.

Example:

CSS
/* Overrides third-party chat widget position */.chat-widget {  bottom: 80px !important;}

or:

CSS
/* Utility state class must override component display rules */.is-hidden {  display: none !important;}

Comments are useful when the CSS would otherwise look strange.

Full Example: Debugging a Component Conflict

HTML:

HTML
<div class=”card featured-card”>  <h2 class=”card-title”>Featured article</h2>  <p class=”card-text”>Read this article first.</p></div>

CSS:

CSS
.card {  color: #333333;  padding: 1rem;} .card .card-title {  color: navy;} .featured-card {  color: darkred;} .featured-card .card-title {  color: red;}

What happens?

The card text inherits darkred from .featured-card.

The title becomes red.

Why?

For the card text:

TEXT
.card sets color: #333333.featured-card sets color: darkredboth apply to the same elementboth are class selectors.featured-card comes laterdarkred wins

For the title:

TEXT
.card .card-title sets navy.featured-card .card-title sets redboth have similar specificity.featured-card .card-title comes laterred wins

This is predictable once you compare the matching declarations.

Full Example: Debugging Inheritance and Direct Styles

HTML:

HTML
<section id=”theme”>  <p class=”note”>This is a note.</p></section>

CSS:

CSS
#theme {  color: green;} .note {  color: purple;}

The paragraph is purple.

Why?

The parent #theme has a more specific selector.

But it styles the parent.

The .note selector styles the paragraph directly.

A direct declaration on the element beats an inherited value from the parent.

This is not a specificity conflict between #theme and .note on the same element.

They apply to different elements.

Full Example: Debugging Layers

HTML:

HTML
<button class=”button text-red”>  Delete</button>

CSS:

CSS
@layer utilities, components; @layer utilities {  .text-red {    color: red;  }} @layer components {  .button {    color: white;  }}

The button text is white.

Why?

The declared layer order is:

CSS
@layer utilities, components;

For normal declarations, later layers have higher priority.

So components beats utilities.

If you want utilities to override components, write:

CSS
@layer components, utilities;

Then the button text becomes red.

Full Example: Debugging !important

HTML:

HTML
<div class=”alert alert-success”>  Saved successfully.</div>

CSS:

CSS
.alert {  color: red !important;} .alert-success {  color: green;}

The text is red.

Why?

The base .alert declaration is important.

The .alert-success declaration is normal.

Important beats normal.

Better CSS:

CSS
.alert {  color: red;} .alert-success {  color: green;}

Now the success modifier can work normally.

Full Example: Debugging Shorthand

HTML:

HTML
<div class=”box”>Box</div>

CSS:

CSS
.box {  border-color: blue;  border-style: solid;  border-width: 4px;} .box {  border: 1px solid gray;}

The final border is 1px solid gray.

Why?

The later border shorthand resets width, style, and colour.

If you want only the colour to change later, write:

CSS
.box {  border: 1px solid gray;} .box {  border-color: blue;}

Now the border is 1px solid blue.

Best Practices for Avoiding Cascade Problems

Use simple class selectors for components.

Keep specificity low.

Avoid unnecessary IDs in CSS.

Avoid long descendant selector chains.

Organise CSS from broad rules to specific rules.

Use source order deliberately.

Put component variants after base component styles.

Use cascade layers for larger projects.

Put resets and vendor styles in low-priority layers.

Put utilities in higher-priority layers if they should override components.

Avoid unnecessary !important.

Use !important only for deliberate utility states, third-party overrides, or exceptional cases.

Use browser developer tools instead of guessing.

Check computed styles to find final values.

Keep related styles close together.

Use clear naming for components and modifiers.

Quick Summary

When a CSS style does not apply, the browser is usually following the cascade.

First, check whether your selector matches the element.

Then check whether the declaration is valid and active.

If the declaration is crossed out, find the winning declaration.

Compare importance, cascade layers, specificity, and source order.

Remember that class order in HTML does not decide which class wins.

Remember that direct styles on an element beat inherited values from a parent.

Remember that parent selector specificity does not transfer to children.

Remember that browser defaults and CSS resets can affect the result.

Remember that media queries only apply when their conditions are true.

Remember that shorthand properties can override longhand properties.

Remember that unlayered CSS can override layered CSS.

Avoid fixing every problem with !important.

Use developer tools, inspect the cascade, and choose the smallest clear fix.

Good CSS debugging is not guessing.

It is finding the winning declaration and understanding why it won.