Specificity is the weight that a browser calculates for a CSS selector to decide which of several conflicting declarations applies to an element. In CSS, when two rules set the same property on the same element with the same origin and importance, the rule with the higher specificity wins, regardless of which one appears later in the stylesheet.
Specificity is one step of the CSS cascade, the algorithm that resolves every style conflict. Understanding it explains why a carefully written rule sometimes has no visible effect, and it is the foundation of maintainable stylesheets that do not depend on ever stronger selectors or !important.
How specificity is calculated
Specificity is expressed as a three-part value written as (A, B, C). The browser counts the components of a selector into three columns and compares the columns from left to right, so a single point in a higher column outweighs any number of points in a lower one.
| Column | What it counts | Examples |
|---|---|---|
| A, ID column | ID selectors | #header |
| B, class column | Classes, attribute selectors, pseudo-classes | .card, [type="text"], :hover |
| C, type column | Type selectors and pseudo-elements | p, li, ::before |
| No weight | Universal selector and combinators | *, >, +, ~, whitespace |
The comparison is not a decimal number. A selector with eleven classes, (0, 11, 0), still loses to a single ID, (1, 0, 0), because columns never carry over. Only when all three columns are equal does the browser fall back to order of appearance, and the later declaration wins.
Worked specificity values
The examples below show how common selectors score and how the newer functional pseudo-classes behave. The :is(), :not() and :has() functions take the specificity of their most specific argument, while :where() always contributes zero specificity.
| Selector | Specificity | Explanation |
|---|---|---|
p | (0, 0, 1) | One type selector |
.nav a | (0, 1, 1) | One class and one type |
.btn.btn-primary:hover | (0, 3, 0) | Two classes and one pseudo-class |
#main .card p | (1, 1, 1) | One ID, one class, one type |
:is(#main, .card) p | (1, 0, 1) | Takes the ID from the strongest argument |
:where(#main, .card) p | (0, 0, 1) | Arguments contribute nothing |
a::before | (0, 0, 2) | Type selector and pseudo-element |
Inline styles written in the style attribute sit above all selector specificity for normal declarations. In the cascade they are often described as an extra column in front of A, although strictly they are handled as a separate step.
Specificity within the CSS cascade
Specificity is only one criterion in the cascade, and it is evaluated after several stronger ones. Many specificity problems are actually cascade problems: a rule loses not because its selector is weak, but because it belongs to a lower cascade layer or lacks importance.
| Step | Criterion | Effect on conflicts |
|---|---|---|
| 1 | Origin and importance | User agent, user and author styles, with important declarations reversed |
| 2 | Context | Shadow DOM encapsulation decides between scopes |
| 3 | Element-attached styles | Inline style declarations beat selector rules |
| 4 | Cascade layers | Rules in later @layer blocks beat earlier layers |
| 5 | Specificity | Higher (A, B, C) wins within the same layer |
| 6 | Order of appearance | The last declaration wins when all else is equal |
Cascade layers, introduced with @layer, changed specificity strategy significantly. A simple class selector in a later layer beats an ID selector in an earlier layer, so teams can place resets, frameworks and components into separate layers and keep selector weight low inside each one.
Managing specificity in real projects
The goal in large codebases is a flat and predictable specificity curve: generic rules with low weight first, more specific component rules later. Methodologies such as BEM achieve this by using single-class selectors almost everywhere, which makes overrides a matter of source order rather than selector strength.
| Situation | Recommended approach | Reason |
|---|---|---|
| Base and reset styles | Wrap in :where() or a low layer | Easy to override later |
| Component styling | One class per element | Equal weight, predictable order |
| Third-party library styles | Import into an early cascade layer | Own styles win without hacks |
| Utility classes | Place in the last layer | Utilities override components reliably |
| Styling by ID | Avoid, use a class instead | IDs create weight that is hard to beat |
| Urgent override | Fix the source rule, not !important | Prevents escalating conflicts |
Common specificity mistakes
Most specificity problems appear gradually, as quick fixes accumulate over months of maintenance. Recognizing the typical patterns early keeps a stylesheet easy to change and prevents the need for a costly rewrite.
- Specificity wars: adding more selectors to win a conflict leads to chains such as
body .page #content .card a, which are fragile and hard to override. - Relying on `!important`: it moves the declaration to a different cascade step, and the next override then also needs
!important. - Assuming inheritance follows specificity: inherited values have no specificity at all, so any rule that targets the element directly wins over an inherited value.
- Misreading `:is()` and `:not()`: one ID inside the argument list raises the weight of the whole selector.
- Debugging by guesswork: browser developer tools list matched rules in cascade order and show which declarations are overridden and why.
Explained without jargon
When two style rules disagree, for example one says a link is blue and another says it is red, the browser needs a way to decide. Specificity is the tie-breaker based on how precisely each rule points at the element.
It is like instructions in an office. A note for all staff says to wear blue, a note for the sales team says gray, and a personal note for one employee says black. The most precisely addressed instruction wins, so that employee wears black. CSS works the same way: rules that name an element more precisely beat general ones.
A typical use case
A front-end developer at an online retailer adds a new .price--sale class to show discounted prices in red. Nothing changes on the product page. Developer tools reveal that an older rule, #product-detail .info span, sets the price color with a specificity of (1, 1, 1), while the new class has only (0, 1, 0).
Instead of escalating with an ID or !important, the developer replaces the old ID-based rule with `.product-info__price` and moves legacy styles into an early cascade layer. The sale class now wins through normal source order, and the team adds a linting rule that forbids ID selectors in stylesheets. Later changes to price styling require one class and no overrides.