CSS Specificity Explained: How Browsers Decide Which Styles Win
CSS specificity is the algorithm browsers use to determine which CSS declaration applies when multiple rules target the same element. Each selector gets a weight score based on its components, and the rule with the highest specificity wins. When two selectors have equal specificity, the one that appears last in the source order takes effect.
Understanding specificity prevents the frustrating experience of writing CSS that seems correct but doesn't apply — and stops the reflex of adding !important to everything.
Quick Answer
Specificity is calculated as a four-part tuple: (inline, IDs, classes, elements).
| Component | Weight | Examples |
|---|---|---|
| Inline styles | 1,0,0,0 |
style="color: red" |
| ID selectors | 0,1,0,0 |
#header, #nav |
| Classes, attributes, pseudo-classes | 0,0,1,0 |
.btn, [type="text"], :hover |
| Elements, pseudo-elements | 0,0,0,1 |
div, p, ::before |
| Universal selector, combinators | 0,0,0,0 |
*, >, +, ~, |
Compare from left to right. The first column with a higher number wins. One ID (0,1,0,0) always beats any number of classes (0,0,99,0).
How the Calculation Works
Step 1: Break the selector into components
Take this selector:
#sidebar .widget h3.title
Break it down:
#sidebar→ 1 ID.widget→ 1 classh3→ 1 element.title→ 1 class
Step 2: Count each category
| Category | Count | Components |
|---|---|---|
| Inline | 0 | — |
| IDs | 1 | #sidebar |
| Classes/attributes/pseudo-classes | 2 | .widget, .title |
| Elements/pseudo-elements | 1 | h3 |
Step 3: Write the specificity
Specificity: 0,1,2,1
This selector will override any selector with specificity 0,1,1,x or 0,0,x,x, regardless of how many classes or elements the other selector has.
Worked Examples
Example 1: Class vs. element
p { color: blue; } /* Specificity: 0,0,0,1 */
.intro { color: green; } /* Specificity: 0,0,1,0 */
For <p class="intro">, the text is green. One class (0,0,1,0) beats one element (0,0,0,1).
Example 2: ID beats everything below it
.nav .item .link { color: blue; } /* Specificity: 0,0,3,0 */
#main-link { color: red; } /* Specificity: 0,1,0,0 */
For <a id="main-link" class="link">, the text is red. A single ID beats any number of classes.
Example 3: Same specificity — order wins
.btn { background: blue; } /* Specificity: 0,0,1,0 */
.primary { background: green; } /* Specificity: 0,0,1,0 */
For <button class="btn primary">, the background is green. When specificity is equal, the last rule in source order wins.
Example 4: Attribute selectors count as classes
input[type="text"] { border: 1px solid gray; } /* 0,0,1,1 */
.form-input { border: 1px solid blue; } /* 0,0,1,0 */
The attribute selector [type="text"] counts the same as a class (0,0,1,0) plus the input element (0,0,0,1), giving 0,0,1,1. It beats .form-input at 0,0,1,0.
Example 5: Pseudo-classes and pseudo-elements
a:hover { color: red; } /* 0,0,1,1 — :hover is a pseudo-class */
a::first-line { color: blue; } /* 0,0,0,2 — ::first-line is a pseudo-element */
Pseudo-classes (:hover, :focus, :nth-child()) count as classes. Pseudo-elements (::before, ::after, ::first-line) count as elements.
What Doesn't Affect Specificity
The universal selector (*)
* { margin: 0; } /* Specificity: 0,0,0,0 */
The universal selector adds zero specificity. It targets everything, but any other selector overrides it.
Combinators
Child (>), descendant ( ), adjacent sibling (+), and general sibling (~) combinators contribute nothing to specificity. They change which elements match, not how strongly the rule applies.
div > p { } /* 0,0,0,2 (div + p, the > adds nothing) */
div p { } /* 0,0,0,2 (same specificity) */
The :where() pseudo-class
:where() is specificity-neutral. Whatever is inside it contributes zero specificity.
:where(.sidebar) p { color: gray; } /* 0,0,0,1 — .sidebar is zeroed out */
.sidebar p { color: blue; } /* 0,0,1,1 */
This is useful for writing default styles that are easy to override.
The :is() and :not() pseudo-classes
Unlike :where(), :is() and :not() take the specificity of their most specific argument.
:is(#header, .nav) a { } /* 0,1,0,1 — uses #header's weight */
:not(.active) { } /* 0,0,1,0 — uses .active's weight */
The !important Exception
!important is not part of the specificity calculation. It's a separate layer that overrides all normal specificity.
p { color: red !important; }
#main p.intro { color: blue; } /* Still red — !important wins */
Priority order (highest to lowest):
!importantdeclarations (user-agent, then user, then author)- Inline styles
- Selector specificity
- Source order
Why !important causes problems
Once you use !important, the only way to override it is with another !important on a more specific selector — or another !important later in source order. This creates escalation:
.btn { background: blue !important; }
.btn.primary { background: green !important; } /* needs !important to win */
#submit.btn { background: red !important; } /* now this too */
Each override needs more specificity and !important. The stylesheet becomes hard to maintain. The fix is almost always to restructure selectors instead.
Inline Styles
Inline styles have a specificity of 1,0,0,0. They beat any selector-based rule.
<p style="color: red" class="intro" id="first">
#first.intro { color: blue; } /* 0,1,1,0 — loses to inline style */
The only way to override an inline style from a stylesheet is !important.
Common Mistakes
Mistake 1: Thinking more classes always win
.a .b .c .d .e .f .g .h .i .j { color: blue; } /* 0,0,10,0 */
#header { color: red; } /* 0,1,0,0 */
Ten classes still lose to one ID. Specificity columns are compared independently, not added together.
Mistake 2: Repeating the same class for specificity
.btn.btn { background: blue; } /* 0,0,2,0 — valid but fragile */
This works to increase specificity, but it's a hack. It makes the intent unclear and surprises other developers. Prefer restructuring your selectors.
Mistake 3: Over-qualifying selectors
div#header ul.nav li.item a.link { } /* 0,1,3,4 */
This selector is unnecessarily specific. It's hard to override later. Prefer flatter selectors:
.nav-link { } /* 0,0,1,0 — easy to work with */
Mistake 4: Confusing source order with specificity
When two rules have the same specificity, the one later in the CSS wins. If your styles aren't applying, check whether a later rule with equal specificity is overriding yours — not just whether a more specific rule exists.
Debugging Specificity Issues
Step 1: Open DevTools
In Chrome, Firefox, or Safari, right-click the element → Inspect. The Styles panel shows all rules targeting the element, ordered by specificity. Overridden properties appear crossed out.
Step 2: Check which rule wins
Look at the crossed-out declarations. The winning rule appears above the losing ones. Hover over the selector to see its specificity in some browsers.
Step 3: Use a specificity calculator
For complex selectors, use a CSS specificity calculator to compare scores side by side. Enter multiple selectors and see which one takes precedence.
Step 4: Simplify
If you're fighting specificity, that's a signal to refactor. Consider:
- Using flatter class-based selectors (BEM, utility classes)
- Removing unnecessary ID selectors from stylesheets
- Using
:where()for resets and defaults - Using CSS layers (
@layer) to manage specificity across codebases
CSS Layers and Specificity
CSS @layer (Cascade Layers) lets you control which group of styles takes precedence, independent of specificity.
@layer base, components, utilities;
@layer base {
a { color: blue; }
}
@layer utilities {
.text-red { color: red; } /* wins over base, regardless of specificity */
}
Layers are resolved before specificity. A rule in a later layer beats a more specific rule in an earlier layer. This is a modern approach to managing large stylesheets without specificity wars.
Practical Tips
-
Prefer classes over IDs in stylesheets. IDs are fine in HTML for anchors and JavaScript hooks. In CSS, they create specificity that's hard to override.
-
Keep selectors flat.
.card-titleis easier to maintain than.page .content .card .card-header .card-title. -
Use
!importantonly for genuine overrides — like a utility class that must always win, or overriding third-party styles you can't change. -
Adopt a naming convention. BEM (
.block__element--modifier) or utility-first approaches naturally keep specificity low and predictable. -
Use
:where()for defaults. Writing:where(.btn) { padding: 0.5rem; }creates styles that any class can override without fighting.
FAQ
What is CSS specificity?
CSS specificity is the algorithm browsers use to determine which CSS rule applies when multiple rules target the same element. Each selector is assigned a weight based on its components (inline styles, IDs, classes, and elements), and the selector with the highest weight wins.
How do I calculate CSS specificity?
Count the number of ID selectors, class selectors (including attribute selectors and pseudo-classes), and element selectors (including pseudo-elements) in the selector. Write them as a tuple like 0,1,2,3. Compare from left to right — the first higher number wins.
Why is my CSS not applying?
The most common reason is a specificity conflict. Another rule with higher specificity is overriding yours. Open browser DevTools, inspect the element, and check which rule is winning. The Styles panel shows all competing rules and crosses out the overridden ones.
Does !important affect specificity?
No. !important operates on a separate level above specificity. An !important declaration always overrides normal declarations, regardless of selector specificity. Among multiple !important declarations, normal specificity rules apply.
Does the universal selector (*) have specificity?
No. The universal selector * has a specificity of 0,0,0,0. Any other selector will override it.
What is the difference between :is() and :where()?
Both accept a selector list and match the same elements. The difference is specificity: :is() takes the specificity of its most specific argument, while :where() always has zero specificity. Use :where() when you want styles that are easy to override.
Can I increase specificity without changing my HTML?
Yes. You can repeat a class (.btn.btn), nest inside :is() or :not(), or use attribute selectors that match the same element. However, if you frequently need to increase specificity, that's a sign to simplify your selector architecture instead.
How do CSS layers (@layer) relate to specificity?
Cascade layers are resolved before specificity. A rule in a later-defined layer beats a more specific rule in an earlier layer. This lets you structure your stylesheet into predictable groups (resets, base, components, utilities) without worrying about selector weight between groups.
Why does one ID beat 100 classes?
Specificity columns are compared independently, not summed. The ID column (0,1,0,0) is compared before the class column (0,0,x,0). Since 1 > 0 in the ID column, the comparison stops there. The number of classes is irrelevant.
What happens when two selectors have the same specificity?
When specificity is identical, the rule that appears last in the CSS source wins. This is sometimes called the "source order" rule and is the final tiebreaker in the CSS cascade.