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:
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.
class="article">
- class="content-block">
+ class="content-block">
Enter fullscreen mode
Exit fullscreen mode
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;
}
Enter fullscreen mode
Exit fullscreen mode
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;
}
Enter fullscreen mode
Exit fullscreen mode
2. Use meaningful class names:
.article-link {
color: blue;
}
Enter fullscreen mode
Exit fullscreen mode
3. Use BEM or CSS Modules if you're in React:
.article__link {
color: blue;
}
Enter fullscreen mode
Exit fullscreen mode
// CSS Modules (React)
styles.link
Enter fullscreen mode
Exit fullscreen mode
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;
}
Enter fullscreen mode
Exit fullscreen mode
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;
}
Enter fullscreen mode
Exit fullscreen mode
Now someone else writes:
.page .button {
background: blue !important;
}
Enter fullscreen mode
Exit fullscreen mode
Then you go:
html body .page .button {
background: green !important;
}
Enter fullscreen mode
Exit fullscreen mode
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;
}
Enter fullscreen mode
Exit fullscreen mode
Result? color: red; still wins.
2. You Start Using !important Everywhere
When you're like:
.button {
background: green !important;
}
Enter fullscreen mode
Exit fullscreen mode
...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 */
}
Enter fullscreen mode
Exit fullscreen mode
Use Class Selectors Over Tags
/* Better than styling all elements */
.card-text {
color: #333;
}
Enter fullscreen mode
Exit fullscreen mode
Keep Specificity Low When You Can
Avoid long chains like:
.main .container .card .title {
font-size: 2rem;
}
Enter fullscreen mode
Exit fullscreen mode
Use classes directly:
.card-title {
font-size: 2rem;
}
Enter fullscreen mode
Exit fullscreen mode
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 Ever
Understand 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;
}
Enter fullscreen mode
Exit fullscreen mode
High specificity
Hard to override
Tightly coupled to DOM structure
Good:
.btn-primary {
background-color: red;
}
Enter fullscreen mode
Exit fullscreen mode
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 Mebutton>
/* CSS */
.btnPrimary {
background-color: red;
}
Enter fullscreen mode
Exit fullscreen mode
Or:
<template>
class="btn-primary">Click Me
template>
<style scoped>
.btn-primary {
background-color: red;
}
style>
Enter fullscreen mode
Exit fullscreen mode
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 {}
Enter fullscreen mode
Exit fullscreen mode
Example:
.card {}
.card__title {}
.card__title--large {}
Enter fullscreen mode
Exit fullscreen mode
class="card">
class="card__title card__title--large">Title
Enter fullscreen mode
Exit fullscreen mode
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');
Enter fullscreen mode
Exit fullscreen mode
In React:
If using BEM with CSS Modules:
<div className={`${styles.card} ${styles['card--highlight']}`}>div>
Enter fullscreen mode
Exit fullscreen mode
But with Tailwind, you skip BEM (class utilities win here):
<div className="bg-white p-4 shadow rounded-lg">div>
Enter fullscreen mode
Exit fullscreen mode
Keep Styles Modular
What does modular mean?
Scoped to 1 component, not global.
Bad:
/* Global styles.css */
h2 {
font-weight: bold;
}
Enter fullscreen mode
Exit fullscreen mode
Might affect all h2s on the site
Easy to cause accidental style leaks
Exception: However, if you're targeting all h2s in your app then it's fine to use it.
Good:
/* UserProfile.module.css */
.user__title {
font-weight: bold;
}
Enter fullscreen mode
Exit fullscreen mode
Only affects UserProfile component
Safe to reuse h2 elsewhere
Frameworks Do This Better
React: Use CSS Modules, Styled Components, or Tailwind
Vue: Use
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;
}
Enter fullscreen mode
Exit fullscreen mode
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, not buttons.css).
React: Prefer CSS Modules / Tailwind over global CSS.
Tailwind: Keep utility classes organized with clsx() or classnames.
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:
Part 1: Why Isn't Your CSS Working? Understanding Specificity
Part 2: Why Isn’t Your CSS Working? Understanding Cascading
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.