What is CSS Specificity? — CSS Selector Priority Explained
What is CSS Specificity? — CSS Selector Priority Explained
Definition
CSS specificity is the algorithm browsers use to determine which CSS property values are most relevant to an element and therefore take precedence when multiple conflicting style declarations target the same element. It is a weight system that assigns a numerical score to each CSS selector based on its composition. The selector with the highest specificity wins when the cascade resolves conflicting rules.
Think of specificity as a tiebreaker in the CSS cascade: when two rules have the same origin and importance, the one with the higher specificity is applied.
The Specificity Hierarchy
CSS specificity is calculated as a four-part value (a, b, c, d) — often represented as a three-part tuple (a, b, c) or calculated as a single number. Each position in the hierarchy carries exponentially more weight than the next:
Level 1: Inline Styles (a)
Inline styles applied directly via the style attribute have the highest specificity. They override any selector-based rule unless !important is used.
| Position | Type | Weight Value |
|---|---|---|
| a | Inline styles (style attribute) | 1,0,0,0 |
Level 2: ID Selectors (b)
Each ID selector (#id) counts toward the second weight column.
| Position | Type | Weight Value |
|---|---|---|
| b | ID selectors (#example) |
0,1,0,0 |
Level 3: Class, Attribute, and Pseudo-Class Selectors (c)
Each class selector (.class), attribute selector ([type="text"]), and pseudo-class (:hover, :nth-child()) counts toward this column.
| Position | Type | Weight Value |
|---|---|---|
| c | Classes, attributes, pseudo-classes | 0,0,1,0 |
Level 4: Element and Pseudo-Element Selectors (d)
Each element selector (div, p) and pseudo-element (::before, ::after) counts toward this column.
| Position | Type | Weight Value |
|---|---|---|
| d | Elements, pseudo-elements | 0,0,0,1 |
How Specificity Is Calculated
The specificity score is computed by counting the number of selectors at each level. The result is a composite value — a four-part comma-separated number like (0, 1, 2, 3).
Calculation Formula
Specificity = (count of inline styles, count of ID selectors, count of class/attribute/pseudo-class selectors, count of element/pseudo-element selectors)
When comparing two selectors, the browser compares left to right. If a differs, the larger a wins. If equal, compare b, and so on.
Important: values at each level are not summed across levels.
0,1,0,0always beats0,0,100,0— one ID always beats any number of classes.
Real Selector Weight Examples
| Selector | Inline | IDs | Classes/Attributes/Pseudo | Elements | Total Specificity |
|---|---|---|---|---|---|
style="color: red" |
1 | 0 | 0 | 0 | 1,0,0,0 |
#sidebar |
0 | 1 | 0 | 0 | 0,1,0,0 |
.menu-item |
0 | 0 | 1 | 0 | 0,0,1,0 |
div |
0 | 0 | 0 | 1 | 0,0,0,1 |
#header .nav a |
0 | 1 | 1 | 1 | 0,1,1,1 |
ul li a.active |
0 | 0 | 1 | 3 | 0,0,1,3 |
div#content article p |
0 | 1 | 0 | 3 | 0,1,0,3 |
a:hover |
0 | 0 | 1 | 1 | 0,0,1,1 |
input[type="text"] |
0 | 0 | 1 | 1 | 0,0,1,1 |
#main .card:nth-child(2) |
0 | 1 | 2 | 0 | 0,1,2,0 |
| `` | 0 | 0 | 0 | 0 | 0,0,0,0 |
Detailed Walkthrough: #header .nav a
#header → 1 ID selector → 0,1,0,0
.nav → 1 class selector → 0,0,1,0
a → 1 element selector → 0,0,0,1
─────────────────────────────────────────────
Total → → 0,1,1,1
Detailed Walkthrough: div#content article p
div → 1 element selector → 0,0,0,1
#content → 1 ID selector → 0,1,0,0
article → 1 element selector → 0,0,0,1
p → 1 element selector → 0,0,0,1
─────────────────────────────────────────────
Total → → 0,1,0,3
The !important Exception
The !important flag is a special declaration that overrides all specificity rules:
.overridden {
color: blue;
}
.override-all {
color: red !important; /* This wins regardless of specificity */
}How !important Changes the Cascade
When !important is used, the cascade order becomes:
- Normal declarations sorted by origin
- Normal declarations sorted by specificity
!importantdeclarations sorted by origin!importantdeclarations sorted by specificity
Rules for !important
!importantdeclarations override all non-important declarations, even inline styles- If two conflicting rules both have
!important, normal specificity rules apply - If two
!importantrules have equal specificity, the one declared later wins
When !important Is Acceptable
- Utility classes (e.g.,
.hidden { display: none !important; }) - Overriding third-party styles when you cannot modify the source
- Print stylesheets that must override screen styles
- Accessibility overrides (e.g., forced visibility)
In most cases, avoid !important — it breaks the natural cascade and makes debugging significantly harder.
The Universal Selector and Combinators
| Selector / Pattern | Specificity Contribution | Notes |
|---|---|---|
* (universal) |
0,0,0,0 | Adds no specificity |
> (child) |
0,0,0,0 | Combinator adds nothing |
+ (adjacent) |
0,0,0,0 | Combinator adds nothing |
~ (sibling) |
0,0,0,0 | Combinator adds nothing |
| `` (descendant) | 0,0,0,0 | Whitespace adds nothing |
:not() |
Counts the argument's specificity | The :not() itself adds nothing |
:is() |
Takes the highest specificity of its arguments | Can cause unexpected weight |
:where() |
0,0,0,0 | Always zero specificity |
Special Pseudo-Classes
:not(selector)— the negation pseudo-class itself adds no specificity, but the selector inside the parentheses is counted normally.:is(selector1, selector2)— takes the specificity of the most specific argument.:where(selector1, selector2)— always contributes 0,0,0,0 regardless of its arguments. Useful for low-specificity overrides.
Common Specificity Issues
1. Overspecified Selectors
Problem: Writing div#content div.container div.row div.col p when .col p would suffice.
Solution: Keep selectors as flat and short as possible. Deeply nested selectors are brittle and harder to override.
2. ID Selectors in Global CSS
Problem: #sidebar .widget-title requires an ID selector to override, forcing future developers to use !important or even more IDs.
Solution: Prefer class selectors over IDs for styling. Reserve IDs for JavaScript hooks or fragment identifiers.
3. Specificity Wars
Problem: Developer A writes .nav-item a, Developer B writes .header .nav-item a to override it, Developer C adds #page .header .nav-item a. Each override increases specificity unnecessarily.
Solution: Agree on a consistent specificity level across the project. Use BEM or similar methodologies to keep specificity flat and predictable.
4. Overusing !important
Problem: A quick !important fix today becomes a debugging nightmare tomorrow when multiple !important declarations conflict.
Solution: Treat !important as a last resort. Refactor the source selector instead of brute-forcing an override.
5. :is() Surprises
/* This has specificity 0,1,0,0 because :is() takes the highest argument */
:is(#sidebar, .widget) a {
color: blue;
}
/* This has specificity 0,0,1,0 */
.widget a {
color: red;
}The first rule wins even though it looks like it should be overridden by the more specific-seeming second rule.
Specificity Comparison Table
| Selector Pattern | Inline | IDs | Classes/Attrs | Elements | Specificity |
|---|---|---|---|---|---|
* { } |
0 | 0 | 0 | 0 | 0,0,0,0 |
p { } |
0 | 0 | 0 | 1 | 0,0,0,1 |
.class { } |
0 | 0 | 1 | 0 | 0,0,1,0 |
p.class { } |
0 | 0 | 1 | 1 | 0,0,1,1 |
#id { } |
0 | 1 | 0 | 0 | 0,1,0,0 |
#id .class { } |
0 | 1 | 1 | 0 | 0,1,1,0 |
#id .class p { } |
0 | 1 | 1 | 1 | 0,1,1,1 |
p.class#id { } |
0 | 1 | 1 | 1 | 0,1,1,1 |
style="color:red" |
1 | 0 | 0 | 0 | 1,0,0,0 |
style + !important |
1 | 0 | 0 | 0 | Wins everything |
p:first-child { } |
0 | 0 | 1 | 1 | 0,0,1,1 |
ul > li { } |
0 | 0 | 0 | 2 | 0,0,0,2 |
a[href*="example"] |
0 | 0 | 1 | 1 | 0,0,1,1 |
div:nth-child(2n+1) |
0 | 0 | 1 | 1 | 0,0,1,1 |
:not(.hidden) { } |
0 | 0 | 1 | 0 | 0,0,1,0 |
:is(#id, .cls) { } |
0 | 1 | 0 | 0 | 0,1,0,0 |
CSS Cascade Order Recap
The specificity algorithm is one step in the broader CSS cascade, which follows this resolution order:
- Origin and importance — browser styles < user styles < author styles < author
!important< user!important< browser!important - Specificity — higher specificity wins
- Source order — later declarations override earlier ones when origin and specificity are equal
Best Practices
1. Use BEM (Block Element Modifier)
BEM naming keeps specificity predictably flat — every selector is exactly one class:
/* BEM: all single classes, specificity always 0,0,1,0 */
.card { }
.card__title { }
.card__title--featured { }
.card__button { }2. Keep Selectors Flat
Avoid nesting beyond 3 levels. Prefer:
.nav-link { } /* good */
.nav-link--active { } /* good */Over:
header nav ul li a.active { } /* overly specific and fragile */3. Avoid ID Selectors for Styling
Use IDs for JavaScript or fragment identifiers. Use classes for CSS. This prevents accidentally creating selectors that are nearly impossible to override.
4. Never Use !important in Application CSS
Reserve !important for utilities and third-party overrides. If you need !important to make your CSS work, your specificity structure needs refactoring.
5. Leverage :where() for Low-Specificity Overrides
/* Both target the same elements, but :where() adds zero specificity */
:where(.card) .title { color: blue; } /* specificity: 0,0,0,1 */
.card .title { color: red; } /* specificity: 0,0,2,0 — wins */6. Test Specificity with Tools
Use a CSS Specificity Calculator to see exact weights before writing rules. This prevents surprises during development.
7. Follow a CSS Methodology
- BEM — Best for component-based architectures
- SMACSS — Categorizes rules into base, layout, module, state, theme
- ITCSS — Layers CSS from lowest to highest specificity
- CSS Modules — Automatically scoped class names
Specificity in Practice: Before and After
The Problem
/* A mess of escalating specificity */
#content .sidebar .widget .title a {
color: blue; /* 0,1,3,1 */
}The Solution
/* Clean, flat, maintainable */
.widget-link {
color: blue; /* 0,0,1,0 */
}
/* Override just as easily */
.dark-theme .widget-link {
color: white; /* 0,0,2,0 */
}Quick Reference: Specificity Cheatsheet
| Want to style... | Use | Specificity |
|---|---|---|
| A specific element | a |
0,0,0,1 |
| A group of elements | .btn |
0,0,1,0 |
| A unique element | #header (avoid if possible) |
0,1,0,0 |
| An element inline | style="" |
1,0,0,0 |
| Everything | * |
0,0,0,0 |
| Skip specificity | !important |
Breaks rules |
LangStop CSS Tools
- CSS Specificity Calculator — Calculate and compare selector weights
- CSS Formatter — Pretty print and beautify CSS
- CSS Validator — Check CSS syntax and errors
- CSS Minifier — Compress and optimize CSS
- CSS Color Converter — Convert between color formats
- CSS Selector Tester — Test which elements a selector matches
- CSS Diff — Compare CSS files side by side
- CSS Variables — Extract and organize CSS custom properties
- CSS Pretty Print — Reformat and clean up CSS