Specificity

On this page 7

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.

ColumnWhat it countsExamples
A, ID columnID selectors#header
B, class columnClasses, attribute selectors, pseudo-classes.card, [type="text"], :hover
C, type columnType selectors and pseudo-elementsp, li, ::before
No weightUniversal selector and combinators*, >, +, ~, whitespace
Specificity components

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.

SelectorSpecificityExplanation
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
Example selectors and their specificity

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.

StepCriterionEffect on conflicts
1Origin and importanceUser agent, user and author styles, with important declarations reversed
2ContextShadow DOM encapsulation decides between scopes
3Element-attached stylesInline style declarations beat selector rules
4Cascade layersRules in later @layer blocks beat earlier layers
5SpecificityHigher (A, B, C) wins within the same layer
6Order of appearanceThe last declaration wins when all else is equal
Order of cascade criteria

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.

SituationRecommended approachReason
Base and reset stylesWrap in :where() or a low layerEasy to override later
Component stylingOne class per elementEqual weight, predictable order
Third-party library stylesImport into an early cascade layerOwn styles win without hacks
Utility classesPlace in the last layerUtilities override components reliably
Styling by IDAvoid, use a class insteadIDs create weight that is hard to beat
Urgent overrideFix the source rule, not !importantPrevents escalating conflicts
Strategies for keeping specificity under control

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.