You know Cascading and Specificity, you write styles accordingly—yet your styles still don’t apply as expected? The issue may not lie in how these concepts work, but in some common pitfalls you might be falling into, knowingly or not.
This article aims to solidify your understanding beyond just theory, showing how these mistakes affect not only CSS, but also frameworks and libraries like React, vue and even vanilla JavaScript.
To help you apply what you've learned, I’ve included 5 real-world quiz scenarios that will deepen your understanding and sharpen your debugging instincts.
This is Part 3 of our “Why Isn’t Your CSS Working?” series. As mentioned in the earlier articles, learning Specificity first makes Cascading easier to grasp—which is why we tackled them in that order. We didn’t stop at theory: we used real examples, DevTools, case studies and investigative studies to connect the dots.
So if you're already comfortable with these two core forces of CSS, you're ready for what’s next.
If not, I highly recommend revisiting:
Because what comes next builds on that foundation—and dives into best practices and common pitfalls that trip up even experienced developers.
Assuming you’ve got those covered—let’s dive in.
Table of Contents
- A Top-Down Look at Common Pitfalls That Break Your CSS
- Using too many ID selectors
- Relying on deeply nested selectors
- Overusing !important
- Forgetting about the cascade
- From Messy to Maintainable: Mastering Specificity, Structure & Scalable CSS
- Bottom Line
- Quick Quiz: What's the Problem & How Would You Fix It?
- Wrap up
- Solutions – Let's Debug Together
A Top-Down Look at Common Pitfalls That Break Your CSS
Before we get hands-on, let’s take a step back and look at some of the most common CSS mistakes that can cause major headaches in real-world projects:
Using too many ID selectors – IDs are powerful but rigid. Classes are more flexible and reusable.
Relying on deeply nested selectors – The deeper the selector, the harder it becomes to override styles cleanly.
Overusing
!important
– It might feel like a quick fix, but it often turns debugging into a nightmare.Forgetting about the cascade – Always check if another rule later in the stylesheet is silently overriding your styles.
Each of these mistakes connects directly to what we’ve already covered about specificity and cascading. So let’s break them down one by one and understand the best practices to avoid them.
1. Using too many ID selectors
Using too many id
selectors will technically work. IDs are unique and valid selectors. The browser doesn’t explode. 😅
But... here’s the catch:
Using too many id
selectors becomes a maintenance nightmare, and can seriously mess with:
1. CSS Specificity Wars
ID selectors are very high in specificity.
Selector Type | Specificity Score |
---|---|
Element | 0-0-1 |
Class | 0-1-0 |
ID | 1-0-0 |
So if you style with:
#myBtn {
background-color: red;
}
And later try:
.btn {
background-color: green;
}
The green won’t apply, unless you do:
button#myBtn.btn {
background-color: green !important;
}
Now you’ve entered the !important vortex of doom💀.
2. Hard to Reuse Styles
IDs are unique, so you can't reuse their styles across multiple components or elements. You’d have to duplicate styles or write new rules for each ID — anti-DRY (Don’t Repeat Yourself).
3. Conflicts in JavaScript & React
In React (and modern JavaScript), you rarely need to use id
s. Since:
- IDs must be unique — hard to guarantee in component-based systems.
- It can break modularity and component isolation.
- You're better off using:
-
ref
s -
data-*
attributes
-
when targeting elements programmatically.
Best Practices
DO | AVOID |
---|---|
Use classes for styling | Relying on too many IDs |
Use id for anchor links (# ) |
Using IDs for layout styling |
Use ref s in React, not id s |
Targeting IDs in deep CSS |
Use BEM or module CSS for scoping | Overwriting IDs with !important
|
Use id
for:
- Navigation targets (
<a href="#contact">
) - ARIA accessibility (
aria-labelledby="#some-id"
) - Unique inputs for labels (
<label for="email">
)
TL;DR:
Too many ID selectors? Technically fine. Practically painful.
Preferclass
,data-*
, orref
for flexibility, reusability, and maintainability.
Pro Tip: Think ofid
as a one-time-use sticky note.
Butclass
? That’s your favorite reusable label-maker.
2. Relying on deeply nested selectors
Relying too much on deeply nested selectors in CSS is like trying to find your socks in a Russian nesting doll. It might work, but it’s a pain to maintain.
Example of Deep Nesting
.wrapper .main-content .article .content-block .paragraph .link {
color: blue;
}
Looks scary? It is
Problems with Deep Nesting
1. Fragile CSS
If the HTML structure changes even slightly, your styles break.
<div class="article">
- <div class="content-block">
+ <section class="content-block"> <!-- Boom, broken -->
Now your styles don’t apply anymore.
2. Poor Reusability
You can't reuse .link
styles outside of that exact hierarchy.
Your CSS becomes tightly coupled to the HTML structure.
3. Specificity Gets Out of Control
The more you nest, the harder it is to override.
/* Good luck overriding this without !important or deep selectors */
.wrapper .main-content .article .content-block .paragraph .link {
color: red;
}
4. Hard to Read & Maintain
Future-you (or your teammates) won’t thank you when they have to unravel spaghetti CSS.
Best Practices
1. Keep selectors shallow:
.link {
color: blue;
}
2. Use meaningful class names:
.article-link {
color: blue;
}
3. Use BEM or CSS Modules if you're in React:
.article__link {
color: blue;
}
// CSS Modules (React)
styles.link
TL;DR:
Deep nesting = CSS quicksand.
Avoid it when possible.
Stick to shallow, reusable, and intentional selectors — and your future self will high-five you 👏.
3. Overusing !important
Now let's talk about !important
— the most powerful and most abused tool in CSS.
What happens when you use !important
?
It forces a style to override anything else — regardless of specificity.
#someId {
color: red !important;
}
This will beat:
- Classes
- Inline styles (unless they also use
!important
) - Even other ID styles without
!important
So… it works. But here’s why it’s dangerous:
1. You break the CSS cascade
CSS is supposed to flow from general → specific → contextual → override.
!important
skips the line like a VIP with no chill.
2. It's hard to debug
You or your team might spend hours yelling at your screen like:
“WHY THE HELL ISN’T THIS STYLE APPLYING???”
...only to realize another rule has!important
.
3. You create a war of !important
escalation
To override this:
.button {
background: red !important;
}
Now someone else writes:
.page .button {
background: blue !important;
}
Then you go:
html body .page .button {
background: green !important;
}
Until everything is a specificity monster. 💀
When is !important
okay?
Good Use Cases | Bad Use Cases |
---|---|
Utility classes (e.g., Tailwind) | Fixing bugs you don’t understand |
Print styles that must override | Overriding your own styles constantly |
External libs you can't control | Using it as a lazy bandaid |
Best Practices
- Use
!important
sparingly — like hot sauce 🌶️: just a dash, or it ruins the whole dish. - Fix the specificity hierarchy instead.
- Use CSS modules, BEM, or scoped styles in React.
- If you're always reaching for
!important
, your CSS architecture needs a rethink.
TL;DR:
!important
is like yelling in CSS — it works, but if everyone starts yelling, it becomes chaos.
Use it rarely. Fix specificity issues instead. Your future self will thank you.
4. Forgetting about the cascade
Forgetting the cascade in CSS is like baking a cake and forgetting it needs layers — it might look okay at first, but then everything collapses into a confusing mess. Let's break this down real smooth.
First: What is the Cascade?
The "C" in CSS — it decides which styles win when multiple rules apply.
It’s based on:
- Source order (last rule wins)
- Specificity
-
Importance (
!important
) - Origin (inline, internal, external)
What Happens If You Ignore It?
1. Confusing Conflicts
You're like: "Why isn't my style working??"
But CSS is like: "Because that earlier, more specific rule beat yours!"
/* Defined early */
.card h2 {
color: red;
}
/* Later but less specific */
h2 {
color: blue;
}
Result? color: red;
still wins.
2. You Start Using !important Everywhere
When you're like:
.button {
background: green !important;
}
...just to "force" styles to apply. But then the next thing needs !important
too. It's a slippery slope.
3. CSS Becomes Unpredictable
Without knowing what overrides what, you end up playing a guessing game instead of writing clean styles.
How to Work With the Cascade
Order Matters
Later rules override earlier ones if specificity is the same.
h1 {
color: blue;
}
h1 {
color: red; /* this one wins */
}
Use Class Selectors Over Tags
/* Better than styling all <p> elements */
.card-text {
color: #333;
}
Keep Specificity Low When You Can
Avoid long chains like:
.main .container .card .title {
font-size: 2rem;
}
Use classes directly:
.card-title {
font-size: 2rem;
}
Best Practices
Bad Practice | Better Practice |
---|---|
Ignoring order | Write styles in order of importance |
Overusing !important
|
Use proper specificity |
Nesting like crazy | Use flat, reusable classes |
TL;DR:
"CSS isn’t broken — you just forgot the rules of the game."
– Every DevTools Console EverUnderstand the cascade: it's the foundation of clean, predictable CSS
Favor class selectors over deep, rigid chains
Respect the order of your stylesheets
Use!important
only when truly necessary
Write CSS like you're handing it off to a tired future-you
From Messy to Maintainable: Mastering Specificity, Structure & Scalable CSS
Now let's dive deep AF into these core CSS practices that separate juniors from pros — whether you’re working in vanilla HTML/CSS/JS or a framework like React, Vue, Angular, Svelte, or even with Tailwind / SCSS / Styled Components.
Keep Specificity Low with Class-Based Styling
Why?
Lower specificity = easier to override styles, fewer !important
wars.
Bad:
#form .button.primary {
background-color: red;
}
- High specificity
- Hard to override
- Tightly coupled to DOM structure
Good:
.btn-primary {
background-color: red;
}
- Reusable anywhere
- Easy to override later with just
.btn-primary
- Easier to scale in teams
In Frameworks (React/Vue/etc.)
Component structure = already scoped
// React + CSS Modules
<button className={styles.btnPrimary}>Click Me</button>
/* CSS */
.btnPrimary {
background-color: red;
}
Or:
<!-- Vue Single File Component -->
<template>
<button class="btn-primary">Click Me</button>
</template>
<style scoped>
.btn-primary {
background-color: red;
}
</style>
Keep classes flat & consistent — don't nest selectors unless truly needed.
Use a Consistent Naming Convention like BEM
Why?
BEM (Block Element Modifier) = modular, clear, predictable.
Structure:
.block {}
.block__element {}
.block--modifier {}
Example:
.card {}
.card__title {}
.card__title--large {}
<div class="card">
<h2 class="card__title card__title--large">Title</h2>
</div>
In React, this helps avoid naming conflicts when using global CSS.
In Vanilla JS:
No magic here — you manually assign classes:
document.querySelector('.card__title').classList.add('card__title--large');
In React:
If using BEM with CSS Modules:
<div className={`${styles.card} ${styles['card--highlight']}`}></div>
But with Tailwind, you skip BEM (class utilities win here):
<div className="bg-white p-4 shadow rounded-lg"></div>
Keep Styles Modular
What does modular mean?
Scoped to 1 component, not global.
Bad:
/* Global styles.css */
h2 {
font-weight: bold;
}
- Might affect all
h2
s on the site - Easy to cause accidental style leaks
-
Exception: However, if you're targeting all
h2
s in your app then it's fine to use it.
Good:
/* UserProfile.module.css */
.user__title {
font-weight: bold;
}
- Only affects
UserProfile
component - Safe to reuse
h2
elsewhere
Frameworks Do This Better
- React: Use CSS Modules, Styled Components, or Tailwind
-
Vue: Use
<style scoped>
-
Angular: Each component has its own
.scss/.css
- Svelte: Styles are scoped by default
Debug Specificity Issues with DevTools
How to Inspect:
- Right-click > Inspect Element
- Go to "Computed" tab
- See which rule applied, and which got overridden (crossed out)
Example:
.button {
color: blue;
}
#main .button.primary {
color: red;
}
In DevTools, you’ll see:
-
color: blue;
crossed out -
color: red;
applied
Learn to use this to:
- Find why your styles aren’t applying
- Identify where you need to reduce specificity
- Avoid using
!important
unless absolutely necessary
TL;DR Dev Wisdom
Anti-Pattern | Best Practice |
---|---|
Deep nested selectors | Flat, class-based styling |
Inline styles everywhere | CSS Modules, Tailwind, SCSS |
Global #ids for everything |
Component-based modular classes |
No naming convention | BEM / consistent class names |
!important wars | Understand and respect the cascade |
Bonus Pro Tips
-
Vanilla: Split your styles by feature, not by type (
form.css
, notbuttons.css
). - React: Prefer CSS Modules / Tailwind over global CSS.
-
Tailwind: Keep utility classes organized with
clsx()
orclassnames
. - SASS: Use nesting sparingly, don’t go beyond 2–3 levels.
Final Words
The difference between messy and clean CSS is not magic — it’s discipline and naming conventions.
Bottom Line
Even if you’ve got specificity and cascading down, CSS still has a few curveballs to throw. In this part of the series, we tackled common pitfalls that quietly break your styles — and how to avoid them like a pro:
Don't Overuse ID Selectors: IDs have heavyweight specificity. If you style with them often, they’ll win every battle — whether you want them to or not. Prefer classes unless you really need an override.
Avoid Deep Nesting: More selectors ≠ more control. Deeply nested styles are fragile, hard to override, and scream “I will break if you change one thing.” Keep it shallow and modular.
Beware of
!important
Abuse: It's a CSS nuke. While useful in rare edge cases, overusing!important
creates debugging nightmares and traps you in a specificity arms race.Think Before You Override Framework Styles: Frameworks like Bootstrap or Material UI bring powerful defaults — but overriding them carelessly can backfire. Learn their structure first, then customize smartly.
Tame Scoped & Component Styles: In React, Vue, or similar setups, styles can be scoped to components or injected dynamically. That’s great — until your class doesn’t apply and you don’t know why. Use DevTools to confirm exactly what’s being rendered and where.
Watch Out for Typos and Mismatches: A lowercase letter, a stray underscore, or the wrong casing can silently kill a style. Always double-check your naming and make DevTools your best friend.
At the end of the day, clean CSS is less about clever hacks and more about predictability, readability, and sanity. Avoid overengineering, keep styles modular, and debug with intention. And remember — CSS isn’t broken, it’s just misunderstood.
Quick Quiz: What's the Problem & How Would You Fix It?
Time to put your CSS sleuthing skills to the test. Below are some broken-style scenarios. For each one, think:
What’s the issue? Why isn’t the CSS working? And how would you fix it?
You’ll find the answers at the end of this article.
Q1.
You wrote a .card
class with new styles, but they aren’t taking effect on your HTML.
You notice there’s already a #card
style declared in the stylesheet.
Why isn’t your .card
class working?
Q2.
You added styles for a button inside a component, but it's showing browser defaults.
The styles are in the parent CSS file.
Why aren’t the styles being applied inside the component?
Q3.
You used Tailwind classes and tried to override one with your own class using !important
,
but it still doesn’t change.
Why didn’t your override work, even with !important
?
Q4.
To fix a layout bug, you added a long selector: div.main > section .profile h2
.
It worked temporarily, but things broke again after a small markup change.
What’s the risk with this fix? How could you approach it better?
Q5.
Your input field has a .form-input
class with custom styles,
but it still shows a thick blue outline and unwanted padding.
What could be causing this? How would you track it down?
Wrap up
Now that we’ve come this far, I hope this article genuinely helps you on your journey as a developer.
I created this series — "Why Isn't Your CSS Working?" — so you don’t have to struggle the way I did during my own CSS learning phase. I was stuck for 4–5 months, often writing CSS rules that simply wouldn’t apply. Out of frustration, I’d sometimes delete all my HTML and CSS and start over.
But once I understood the two core forces — Specificity and Cascading — and learned how to debug with DevTools, everything started making sense. With time and experience, I learned how to write clean, predictable, and robust CSS.
Of course, UI design isn’t easy — especially when you’re aiming for something unique and creative. But writing CSS, or recreating a designer’s layout, becomes much more manageable once you're comfortable with how cascading and specificity work under the hood.
Thanks so much for reading till the end — I truly appreciate it. 🙏
If this series helped you, consider sharing it with someone else who’s battling stubborn CSS. Let’s make debugging a little less painful for everyone.
Catch up on previous parts:
- Cover image designed by Anique azar
Solutions – Let's Debug Together
Let’s look under the hood and solve each mystery one by one:
A1. ID trumps class
The existing #card
rule has higher specificity than your .card
class.
CSS specificity means your class styles are being overridden.
Fix:
Avoid using ID selectors for styling. Use class names instead.
Or, if needed, increase the specificity of your class selector.
A2. Scoped or isolated styles
In frameworks like React or Vue, styles may be scoped to a component.
Global styles from a parent file won’t automatically apply inside unless imported correctly.
Fix:
Ensure your styles are available in the component,
or move them into the component’s own scoped CSS/module file.
A3. Tailwind’s built-in !important
Tailwind uses !important
under the hood.
If your override doesn’t match that level of specificity and importance, it won't win.
Fix:
Use your own !important
, increase specificity, or use inline styles carefully.
A4. Overly deep nesting
Selectors like div.main > section .profile h2
are fragile —
they rely on a very specific HTML structure and break with small changes.
Fix:
Keep selectors shallow and modular. Use utility classes or scoped styles instead.
A5. Browser or framework styles interfering
The thick blue outline and padding likely come from default browser styles
or something like Bootstrap’s default form styles.
Fix:
Inspect the element in DevTools, identify the source of the style,
and override it with a CSS reset, normalize.css, or custom utilities.
Top comments (0)