Ever felt like screaming at your CSS because it won’t apply your styles? You tweak a color, refresh… nothing. Or worse, some rule overrides it. The culprits? CSS Cascading and Specificity—the forces deciding which styles win.

Specificity isn’t just a beginner’s hurdle—I’ve seen it frustrate seasoned developers as well. I’ve been there myself.

This is part one of our “Why Isn’t Your CSS Working?” series. Before tackling cascading, you must master specificity, the ranking system that decides which styles apply over others.

Many tutorials teach cascading first, but without understanding specificity, it feels unpredictable. Mastering it now makes cascading much clearer in the next part.

While writing this article, I considered all the real-world problems I’ve faced—many of which both beginners and advanced devs deal with daily. At the end of this article, you’ll get a case study to cement your understanding and a quiz to test it.

This is theory-heavy, but trust me, it’ll make CSS far less frustrating in the future. Let’s dive in!



Table of Contents

  1. What is Specificity?
  2. Specificity Calculation (The Simple Formula) In Depth
  3. How Combined Selectors Affect Specificity Ranking
  4. Specificity of Inline CSS, Internal CSS, External CSS
  5. An Exception in CSS Specificity – !important
  6. Specificity in Action (A Case Study)
  7. Bottom Line
  8. Quick Quiz: Strengthen Your Understanding of the Specificity
  9. Wrapping Up
  10. SOLUTION OF THE QUIZ


What is Specificity?

Specificity determines which CSS rule takes precedence when multiple rules apply to the same element. The general rule is: more specific rules override less specific ones.

Think of specificity as a ranking system. For example, in the military, a lieutenant can give orders in the absence of a general. However, if the general is present, their orders take priority over the lieutenant’s because they hold a higher rank. Similarly, in CSS, a more specific rule will always override a less specific one.



Specificity Calculation (The Simple Formula) in depth

Now that you understand what specificity is, let’s break down how it’s calculated. The easiest way to think about specificity is to assign a score to each type of selector.

We’ll use a ranking system to make specificity easier to understand. If we assign ranks to a a selector:

  • A → High Rank
  • B → Medium Rank
  • C → Low Rank
  • If combined, the ranking increasesAB is stronger than A or AC, just as BC is stronger than B or C.
  • If the same rank appears multiple times, the last one takes priority → For example, if AB appears first and another AB appears later, the second one will override the first. (Which is known as cascading, which will be discussed in the next part of this series.)

The Specificity Formula

(ID count, Class/Attribute/Pseudo-class count, Element/Pseudo-element count)

Feeling overwhelmed? No worries! Let’s break it down step by step.


1. Understanding Specificity of an ID Selector

Suppose we write CSS for an element using an ID selector:

#myElement {
    color: blue;
}

Let’s apply the specificity formula by replacing the values:

  • ID count = 1 (since we are using a single ID selector)
  • Class/Attribute/Pseudo-class count = 0 (since no classes, attributes, or pseudo-classes are used)
  • Element/Pseudo-element count = 0 (since no element names or pseudo-elements are used)

Thus, the specificity value is:

(1, 0, 0)

Why does 1 in place of The ID count appear in the first position?

CSS specificity prioritizes ID selectors, giving them the highest weight. That’s why they always take the first position in the specificity tuple.

Since ID selectors uniquely identify elements, they have high specificity, so we assign them Rank A.

This is the specificity of an ID selector. This is easier than it looks, isn't it?


2. Understanding Specificity of Class/Attribute/Pseudo-class Selector

Now that we’ve assigned values for an ID selector, understanding Class, Attribute, and Pseudo-class selectors becomes easier. Let’s start with the Class Selector.

1. Class Selector

Here’s our specificity formula again:

(ID count, Class/Attribute/Pseudo-class count, Element/Pseudo-element count)

For class selectors, we replace the Class/Attribute/Pseudo-class count portion with 1, just like we did for ID selectors, but in its respective position.

Let’s see it in action:

/* Class Selector Example */
.button {
    color: blue;
}

Since we are selecting an element by its class name, we assign:

  • ID count = 0 (No ID selector used)
  • Class/Attribute/Pseudo-class count = 1 (One class selector used)
  • Element/Pseudo-element count = 0 (No element or pseudo-element used)

Thus, the specificity value is:

(0, 1, 0)

Since class selectors have a lower specificity than ID selectors, we assign them Rank B.


2. Attribute Selector

/* Attribute Selector Example */
input[type="text"] {
    border: 1px solid red;
}

For an attribute selector like [type="text"], the specificity is:

(0, 1, 0)

Because:

  • ID count = 0 (No ID selector used)
  • Class/Attribute/Pseudo-class count = 1 (One attribute selector used)
  • Element/Pseudo-element count = 0 (No element or pseudo-element used)

Since it has the same specificity as a class selector, we also assign it Rank B.


3. Pseudo-class Selector

/* Pseudo-class Selector Example */
a:hover {
    text-decoration: underline;
}

A pseudo-class like :hover follows the same rule:

(0, 1, 0)
  • ID count = 0 (No ID selector used)
  • Class/Attribute/Pseudo-class count = 1 (One pseudo-class used)
  • Element/Pseudo-element count = 0 (No element or pseudo-element used)

Just like class and attribute selectors, pseudo-classes hold Rank B.

Why does 1 in place of Class/Attribute/Pseudo-class count appear in the second position?

CSS specificity prioritizes Class, Attribute, and Pseudo-class selectors after ID selectors, giving them the second highest weight. That’s why they always take the second position in the specificity tuple.

This covers the Class, Attribute, and Pseudo-class selectors. Their specificity is equal, making them more powerful than element selectors but weaker than ID selectors.


3. Understanding Specificity of Element/Pseudo-element Selector

Now that we’ve covered Class, Attribute, and Pseudo-class selectors, understanding Element and Pseudo-element selectors is straightforward. These selectors have the lowest specificity in CSS.

1. Element Selector

Here’s our specificity formula again:

(ID count, Class/Attribute/Pseudo-class count, Element/Pseudo-element count)

For element selectors, we replace the Element/Pseudo-element count portion with 1, while keeping the other values as 0.

Let’s see it in action:

/* Element Selector Example */
p {
    font-size: 16px;
}

Since we are selecting an element by its tag name, we assign:

  • ID count = 0 (No ID selector used)
  • Class/Attribute/Pseudo-class count = 0 (No class, attribute, or pseudo-class used)
  • Element/Pseudo-element count = 1 (One element selector used)

Thus, the specificity value is:

(0, 0, 1)

Since element selectors have the lowest specificity, we assign them Rank C.


2. Pseudo-element Selector

/* Pseudo-element Selector Example */
p::before {
    content: "Note: ";
    font-weight: bold;
}

A pseudo-element like ::before or ::after follows the same rule:

(0, 0, 1)
  • ID count = 0 (No ID selector used)
  • Class/Attribute/Pseudo-class count = 0 (No class, attribute, or pseudo-class used)
  • Element/Pseudo-element count = 1 (One pseudo-element used)

Since pseudo-elements behave like elements in specificity calculations, they share Rank C with element selectors.

Why does 1 in place of Element/Pseudo-element count appear in the third position?

CSS specificity does not prioritize Element/Pseudo-element selectors as highly as Class, Attribute, Pseudo-class, or ID selectors, giving them the lowest weight. That’s why they always take the third position in the specificity tuple.

Still, there is a lower level of specificity available—the most lowest—and we are going to explore it in this next section.


4. Understanding Specificity of the Universal Selector

Now that we've explored ID selectors, class/attribute/pseudo-class selectors, and element/pseudo-element selectors, let's look at the universal selector *.

The universal selector applies to all elements, but it holds the lowest possible specificity in CSS.

Let’s revisit our specificity formula:

(ID count, Class/Attribute/Pseudo-class count, Element/Pseudo-element count)

Universal Selector Example

/* Universal Selector Example */
* {
    margin: 0;
    padding: 0;
}

Let’s break this down using the specificity formula:

  • ID count = 0 (No ID selector used)
  • Class/Attribute/Pseudo-class count = 0 (No class, attribute, or pseudo-class used)
  • Element/Pseudo-element count = 0 (No element or pseudo-element used)

So the specificity of the universal selector is:

(0, 0, 0)

Why is the universal selector the weakest?

The universal selector * is designed to target everything, but with no priority. It acts like a default fallback. That’s why it holds zero specificity — it’s not targeting any specific type, class, or ID.

You can think of it as a private in the military ranking analogy — it follows all orders but can't override anyone.


Finalizing CSS Ranking System:

  • Rank AID selectors (1, 0, 0)
  • Rank BClass, Attribute, Pseudo-class selectors (0, 1, 0)
  • Rank CElement, Pseudo-element selectors (0, 0, 1)

We now understand that Rank A (ID selectors) overrides Rank B (Class, Attribute, Pseudo-class selectors), which in turn overrides Rank C (Element, Pseudo-element selectors) — and all universal selectors get overridden by all of them.

But what happens when we combine multiple selectors? Can a compound selector (a combination of classes, attributes, or pseudo-classes) override an ID selector?



How Combined Selectors Affect Specificity Ranking

We have seen how different ranks (A, B, and C) influence which styles take precedence. But what happens when we combine or repeat them?

1. Combination of A and B (AB)

When A (high rank) and B (medium rank) are combined, the result is stronger than A or B alone. This makes AB more dominant than a standalone A or B.

#id.class {
  color: blue;
}

Or Descendant Selector (A B)

.container p {
  color: blue;
}

or Child Selector (A > B)

.container > p {
  color: red;
}

Or Adjacent Sibling Selector (A + B)

h2 + p {
  margin-top: 0;
}

Or General Sibling Selector (A ~ B)

h2 ~ p {
  color: gray;
}
  • Rank: (1, 1, 0)
  • Since it contains both A and B, it is stronger than a single A or B.

2. Combination of B and C (BC)

When B (medium rank) and C (low rank) are combined, the result becomes stronger than B or C alone, but still weaker than any selector containing A.

.class div {
  font-size: 16px;
}

Or Descendant Selector (B C)

.container span {
  color: blue;
}

Or Child Selector (B > C)

.box > h2 {
  color: red;
}

Or Adjacent Sibling Selector (B + C)

.note + h4 {
  margin: 10px 0;
}

Or General Sibling Selector (B ~ C)

.alert ~ h2 {
  font-weight: bold;
}
  • Rank: (0, 1, 1)
  • Since it contains both a class and an element selector, it’s stronger than either alone, but cannot beat a selector with an ID.

3. Repeating A (AA)

If A (ID selectors) appears more than once in a selector chain, the specificity doubles, making it stronger than any combination of B and C, but with one catch: you cannot apply more than one ID to a single element in valid HTML.

Invalid example (for concept only):

#id1#id2 {
  background: red;
}

Valid and practical form:

id="container">
   id="item">Hello

Is Descendant Selector (A A)

#container #item {
  color: green;
}

Or Child Selector (A > A)

#parent > #child {
  font-size: 18px;
}

Or Adjacent Sibling Selector (A + A)

#heading + #subheading {
  margin-top: 0;
}

Or General Sibling Selector (A ~ A)

#main ~ #footer {
  color: gray;
}
  • Rank: (2, 0, 0)
  • Contains two A selectors — very high specificity, but only valid if used on different elements in hierarchy.

4. Repeating B (BB)

If B (class selectors) are repeated within a selector, the specificity gains weight, becoming stronger than a single B, and even combinations like BC, but it’s still weaker than A-based selectors.

.class1.class2 {
  border: 1px solid black;
}

Or Descendant Selector (B B)

.card .highlight {
  background: yellow;
}

Or Child Selector (B > B)

.wrapper > .info {
  color: red;
}

Or Adjacent Sibling Selector (B + B)

.title + .subtitle {
  margin-left: 10px;
}

Or General Sibling Selector (B ~ B)

.alert ~ .message {
  padding: 10px;
}
  • Rank: (0, 2, 0)
  • Combining multiple class selectors gives a stronger edge than a single class or element — but not strong enough to beat an ID selector.

5. Repeating C (CC)

When C (element selectors) are repeated or chained, they gain a little specificity boost — but even multiple elements remain the weakest in the specificity hierarchy.

You cannot combine it like:

sectionarticle {
  font-size: 18px;
}

Beacause sectionarticle is an invalid HTML tag.

Only Descendant Selector (C C)

section article {
  font-size: 18px;
}

Or Child Selector (C > C)

ul > li {
  list-style: none;
}

Or Adjacent Sibling Selector (C + C)

h1 + h2 {
  color: red;
}

Or General Sibling Selector (C ~ C)

p ~ span {
  font-weight: bold;
}
  • Rank: (0, 0, 2)
  • Repeating element selectors increases weight slightly, but they are still easily overridden by any class or ID selector.


Specificity of Inline CSS, Internal CSS, External CSS

You can add CSS to an HTML document in three ways:

1. External CSS – written in a separate .css file and linked using the tag.

rel="stylesheet" href="styles.css">
  • Specificity: External CSS has the lowest specificity if it uses general selectors. However, its rules can still override others depending on how specific the selectors are — which we have already seen above, and through the use of the !important keyword — which we will see shortly.

2. Internal CSS – written inside tags within the section of the HTML document.

div {
      width: 400px;
      background-color: green;
    }
  • Specificity: Internal CSS has medium specificity. It overrides external if the selector is the same. Otherwise, higher specificity of external CSS can override internal CSS styles. It also gets overridden by inline styles for the same element.

3. Inline CSS – written directly in an HTML element’s style attribute.

style="color: red;">This is Inline CSS.
  • Specificity: Inline CSS has the highest specificity. It overrides both internal and external styles applied to the same element — even if the element is targeted using an ID selector or a combination of ID or class selectors.

However, there is one exception — the !important keyword, which can override inline styles if applied properly.



An Exception in CSS Specificity – !important

Normally, inline CSS has the highest specificity compared to internal and external styles.

However, there's an important exception — the !important declaration.

When you add !important to a CSS rule, it tells the browser:

"No matter what the specificity is, apply this rule."

Even inline styles will be overridden by a rule marked !important.

Example:

#text_id {
    color: blue !important;
  }


 id="text_id" style="color: red;">
  This will appear in blue, not red.

How !important Works

  • It boosts the priority of a CSS declaration above others — including inline styles, ID selectors, class selectors, and element selectors.
  • It does not change the specificity value of a rule — it simply forces the rule to win in the cascade.
  • If multiple !important rules apply to the same property, then specificity comes back into play — the more specific selector wins.

(Don’t worry about Cascading as it’s the subject of our next part of this series, where we’ll understand it in depth through an investigative case study.)

Now that you understand specificity in depth, let’s do a case study to explore its practical implementation.



Specificity in Action (A Case Study)

Let’s write a basic

element with some lorem text. We’ll use this element across all the cases below:

A screenshot of a p element with class and id attributes and some lorem text


Case I – Apply color green using an element selector:

A screenshot of color win to the p element using an element selector

Specificity is : (0, 0, 1)

It worked — as expected. If you’ve correctly linked your .css file, this should always work.

But what happens if we apply a class selector to this same element while keeping the element selector as well?

Let’s find out.


Case II – Give the same element color blue using a class selector, while the element selector is still win

A screenshot of color win to the p element using a class selector instead of element selector

Specificity is : (0, 1, 0)

Before it comes to your mind why I used the class selector above the element selector in the stylesheet — even though we applied the element selector first — let me clarify:

This is due to cascading, which we’ll fully cover in the next part of this series.

But to give you a quick preview — cascading is a rule that applies CSS based on the order of appearance when two selectors have equal specificity. For example:

p {
  color: red;
}
p {
  color: yellow;
}

The yellow color will be applied because it comes last.

However, in this case, the class selector has higher specificity than the element selector, so the text color in the

element is changed to blue.


Case III – Give the same element color red using an ID selector, while the class and the element selector are still win

A screenshot of color win to the p element using an id selector instead of class selector and element selector

Specificity is : (1, 0, 0)

As you can see, the ID selector overrides all the selectors even though they appear later in the stylesheet.

But what if we use a combined class selector? Will it win over the ID selector?


Case IV – Give the same element color cyan using a combined class selector, while the ID, the class, and the element selector are still win. Then remove the ID selector to see what happens.

A screenshot of color win to the p element using an combined class selector but id selector take the priority instead of class selector and element selector

Specificity is: (1, 2, 0)

The class selector gets a score of 2 because we used a combined class selector (.class1.class2), and the ID selector gets a score of 1 — however, the ID selector still wins.

Why? Because ID specificity (1, 0, 0) is always higher than any number of class selectors.

But what if we comment out the ID selector?

Let's see:

A screenshot of color win to the p element using an combined class selector while id selector is commented instead of class selector and element selector

Specificity is: (0, 2, 0)

So now that the ID selector is removed, the combined class selector wins — and the color changes to cyan.

No matter how many classes, attributes, or elements you combine — an ID selector will always win if it’s present.


Case V – Apply the color goldenrod using a combined selector of ID and class, while the ID, class, and element selectors are still applied.

A screenshot of color applied to the p element using a combined ID and class selector instead of class and element

Specificity is: (1, 1, 0)

Since the selector combines both an ID and a class, the specificity becomes (1, 1, 0), which is more specific than just the ID alone (1, 0, 0).

That’s why goldenrod wins instead of red.

Combining selectors increases specificity — making your rule stronger in the cascade.

Case VI – Use Internal CSS with the same selector (#text_id.text_class), applying the color cadetblue

Why are we using the same selector (#text_id.text_class)?

Because if we use only the ID selector (#text_id), it has lower specificity than the combined ID and class selector (#text_id.text_class).

So no matter where we use #text_id, it will be overridden by the more specific #text_id.text_class.

Now let’s apply #text_id.text_class in Internal CSS and change the color to cadetblue — see what happens:

A screenshot of color applied to the p element using internal CSS with a combined ID and class selector

If we target an element with the same selector in Internal CSS, it has higher priority than External CSS. So, the color cadetblue wins.

However, it still gets overridden by inline styles on the same element.

Case VII – Use Inline CSS for the same element, applying the color grey

A screenshot of color applied to the p element using inline CSS which overrides all other styles

As you can see, inline CSS, with style attribute, overrides everything — external, internal, or embedded styles — even selectors with high specificity, and color grey wins.

But wait, there’s one thing that can break inline style priority:

A keyword called !important.

Case VIII – Use a CSS specificity exception (!important) with the element selector, applying the color green

A screenshot of color applied to the p element using an element selector with !important which overrides even inline styles

The keyword !important breaks all the rules of specificity, so the color green defined with the

(element) selector is applied.

When you add !important to a CSS rule, it tells the browser:

"No matter what the specificity is, apply this rule."

Even inline styles get overridden by a low-specificity rule (like an element selector) if it’s marked !important.



Bottom Line

CSS specificity determines which rule wins when multiple rules target the same element. Here's a recap of what we learned:

  • Element selectors have the lowest specificity — they work until something more specific comes along.
  • Class selectors override element selectors because they have higher specificity.
  • ID selectors beat both element and class selectors — no matter where they appear in the stylesheet.
  • Combined selectors (like .class1.class2 or #id.class) have higher specificity than their individual parts. More selectors = more specific.
  • Internal CSS (within ) can override external CSS if specificity is equal, due to its higher location in the cascade.
  • Inline styles override both external and internal styles, regardless of their specificity.
  • The !important keyword breaks all specificity rules. It forces a style to apply, even beating inline styles — no matter the selector's specificity.

Think of specificity like weight classes — the heavier rule always wins.

And !important is like a cheat code — use it only when absolutely necessary, as it can make debugging harder.



Quick Quiz: Strengthen Your Understanding of the Specificity

Question:

What color will the

element render in the browser?

class="title" id="header">Hello
#header h1 { color: blue; }
.title { color: red; }
h1 { color: green; }


Wrapping Up

That’s the end of this Part One article of the “Why Isn’t Your CSS Working?” series.

Here, we’ve covered specificity in depth — from element, class, ID, to inline and even !important.

But this is only half the picture.

Next, we’ll explore the second half of the puzzle: Cascading — the rule of order.

Because Specificity and Cascading are the soulmates of CSS — understanding one without the other still leads to confusion.


I hope this helped you in your web development journey.

If you have any confusion, corrections, or suggestions, feel free to drop a comment below!

Thanks for reading.



SOLUTION OF THE QUIZ

Answer: Red


Explanation

Let’s break down the specificity of each selector:

  1. #header h1 → Specificity: (1, 0, 1)

    This targets an

    inside an element with the ID header.

    But in our HTML, the

    itself has the ID header, it’s not inside an element with that ID.

    So this rule does not apply.

  2. .title → Specificity: (0, 1, 0)

    This applies to any element with the class title.

    The

    has this class, so this rule applies.

  3. h1 → Specificity: (0, 0, 1)

    This applies to all

    tags.

    So it also applies.

Now, between .title and h1, the .title selector has higher specificity, so its color red wins.