DEV Community

Cover image for Why Isn't Your CSS Working? Common Pitfalls and Best Practices
Md Umar Siddique
Md Umar Siddique

Posted on • Edited on

Why Isn't Your CSS Working? Common Pitfalls and Best Practices

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

  1. A Top-Down Look at Common Pitfalls That Break Your CSS
  2. Using too many ID selectors
  3. Relying on deeply nested selectors
  4. Overusing !important
  5. Forgetting about the cascade
  6. From Messy to Maintainable: Mastering Specificity, Structure & Scalable CSS
  7. Bottom Line
  8. Quick Quiz: What's the Problem & How Would You Fix It?
  9. Wrap up
  10. 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:

  1. Using too many ID selectors – IDs are powerful but rigid. Classes are more flexible and reusable.

  2. Relying on deeply nested selectors – The deeper the selector, the harder it becomes to override styles cleanly.

  3. Overusing !important – It might feel like a quick fix, but it often turns debugging into a nightmare.

  4. 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;
}
Enter fullscreen mode Exit fullscreen mode

And later try:

.btn {
  background-color: green;
}
Enter fullscreen mode Exit fullscreen mode

The green won’t apply, unless you do:

button#myBtn.btn {
  background-color: green !important;
}
Enter fullscreen mode Exit fullscreen mode

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 ids. Since:

  1. IDs must be unique — hard to guarantee in component-based systems.
  2. It can break modularity and component isolation.
  3. You're better off using:
    • refs
    • 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 refs in React, not ids 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.

Prefer class, data-*, or ref for flexibility, reusability, and maintainability.

Pro Tip: Think of id as a one-time-use sticky note.

But class? 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;
}
Enter fullscreen mode Exit fullscreen mode

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 -->
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:

  1. Source order (last rule wins)
  2. Specificity
  3. Importance (!important)
  4. 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 <p> 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 Me</button>

/* CSS */
.btnPrimary {
  background-color: red;
}
Enter fullscreen mode Exit fullscreen mode

Or:

<!-- Vue Single File Component -->
<template>
  <button class="btn-primary">Click Me</button>
</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
<div class="card">
  <h2 class="card__title card__title--large">Title</h2>
</div>
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 <style scoped>
  • Angular: Each component has its own .scss/.css
  • Svelte: Styles are scoped by default

Debug Specificity Issues with DevTools

How to Inspect:

  1. Right-click > Inspect Element
  2. Go to "Computed" tab
  3. 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 forcesSpecificity 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:


VISIT MY GITHUB

VISIT MY LINKEDIN

VISIT MY TWITTER

- 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)