CSS Specificity: How the Cascade Picks the Winning Rule
Specificity is the score the browser gives each selector to decide which rule wins when several of them set the same property on the same element. This guide is part of our CSS Selectors series.
Syntax
/* the browser reads each selector as three tiers: */
/* (ids, classes/attributes/pseudo-classes, types/pseudo-elements) */
#main /* (1, 0, 0) */
.card .title /* (0, 2, 0) */
a:hover /* (0, 1, 1) */
ul li /* (0, 0, 2) */ How Is CSS Specificity Calculated?
CSS specificity counts a selector across three columns and compares them from left to right. An id is worth one point in the first bucket, a class or attribute or pseudo-class is worth one in the second, and a tag or pseudo-element is worth one in the third:
#promo { /* (1, 0, 0) */
color: red;
}
.banner .text { /* (0, 2, 0) */
color: blue;
} A single id beats any number of classes, because the first bucket is compared before the second one even matters. So #promo wins the color here despite .banner .text carrying two classes. The comparison never adds the buckets together into one number, it walks them in order until one selector pulls ahead.
Inline styles and the universal selector sit outside this same three-tier count. An inline style attribute on an element beats every normal declaration in your stylesheet regardless of score, though a rule marked !important can still override it, so inline values can make theme overrides harder. The universal selector, *, sits at the opposite extreme and scores (0, 0, 0), so it loses to nearly everything.
When Does Source Order Break a CSS Tie?
Source order breaks a CSS tie when origin, importance, layer, specificity, and scoping proximity are equal. The later valid declaration wins for that property. In this unlayered example, both selectors have the same weight, so the second background applies.
.button {
background: gray;
}
.button {
background: green;
} Both selectors score (0, 1, 0), so the second one wins and the button comes out green. Load order across files counts too, which is how a stylesheet linked later can quietly override an earlier one without any change in specificity.
Source order applies across every stylesheet the page loads, spanning all of them as a single sequence. If your build tool concatenates or bundles files in a different order than you expect, two selectors that tie on specificity can swap which one wins without a single line of CSS changing. That is a common cause of a rule that works locally but flips in production.
How Does !important Affect the CSS Cascade?
CSS !important gives a declaration precedence over normal declarations from the same author origin, before specificity is compared. Between important declarations, origin and layer order still matter. A larger specificity score cannot overcome those earlier decisions.
.alert {
color: black !important;
}
#urgent .alert {
color: red; /* loses, even with an id, because the other rule is !important */
} The #urgent .alert rule has a higher raw score, yet it still loses because the other declaration is marked important. When you find yourself stacking !important to win, slow down and fix the selector or the load order instead. The selector basics guide covers why leaning on ids creates this kind of trap in the first place.
Among important declarations in the same origin and layer, specificity and then the remaining cascade tie-breaks decide the winner. Important layer order is reversed, as the layer example below explains. Inspect the cascade before increasing a selector’s weight.
How Does :where() Keep Specificity Low?
CSS :where() adds zero specificity, including the selectors inside its parentheses. That makes it useful for defaults you want other rules to override. Selectors outside the parentheses still contribute their normal weight.
:where(#sidebar, .widget) a { /* (0, 0, 1) — only the a counts */
color: navy;
}
#sidebar a { /* (1, 0, 1) */
color: teal;
} The :where(#sidebar, .widget) part contributes zero specificity, but the trailing a still contributes one type selector. A class selector can override this normal rule within the same cascade layer. That keeps defaults easier to change.
Its sibling :is() behaves differently and the difference catches people out. :is() takes the specificity of the most specific selector inside it, so :is(#sidebar, .widget) scores as an id even when it matched on the class. The same rule applies to :not() and :has(). Reach for :is() when you want the grouping to be concise, and :where() when you want the grouping to be weak.
Do Cascade Layers Override Specificity?
CSS cascade layers take precedence over selector specificity within the same origin and importance level. For ordinary declarations, a rule in a later layer beats a rule in an earlier one whatever their scores:
@layer base, theme;
@layer base {
#sidebar .widget a { color: navy; } /* (1, 1, 1) */
}
@layer theme {
a { color: teal; } /* (0, 0, 1) — wins */
} The plain a selector wins because theme is declared after base, and the layer comparison happens before either score is looked at. Declarations outside any layer are stronger still, which is worth knowing before you wrap an existing stylesheet in a layer and watch its precedence change.
All of that describes normal declarations, and !important turns it upside down. Among important declarations the layer order reverses, so an important rule in an earlier layer wins, and important rules inside a layer outrank important rules outside every layer. The practical reading is that layers give you a deliberate ordering for your normal styles and a mirror image of it for your emergencies, which is another reason not to reach for !important to settle a layered stylesheet. The CSS guide sets out the full order the cascade works through.
Between them, these two features replace most of what !important used to be needed for. When a rule refuses to apply, the useful question is no longer "how do I score higher" but "which stage is this being decided at", because adding weight to a selector cannot win an argument that was settled a stage earlier.
Example
#sidebar .widget a { /* (1, 1, 1) */
color: navy;
}
.widget a { /* (0, 1, 1) */
color: teal;
} <aside id="sidebar">
<div class="widget"><a href="/">Home</a></div>
</aside> The link comes out navy because the first selector carries an id and scores (1, 1, 1), which beats the id-free (0, 1, 1) of the second rule. Drop the #sidebar from that first selector and both rules tie, at which point source order takes over and the later one wins. Once you can read those three numbers off a selector, the cascade stops feeling random.
Frequently Asked Questions
Does the order of class names in the HTML attribute change which rule wins?
The order of class names in an HTML class attribute does not affect CSS precedence. The cascade resolves conflicts using origin, importance, layers, specificity, scoping proximity, and source order rather than the order of those class names.
Do CSS cascade layers change how specificity works?
CSS cascade layers are compared before specificity. For normal author declarations, later layers beat earlier layers and unlayered styles beat layered styles. Important declarations reverse that layer order, so specificity alone cannot explain which rule wins.
Does the :where() pseudo-class affect specificity?
CSS :where() and its arguments always contribute zero specificity. Selectors outside :where() still count. By comparison, :is() contributes the specificity of its most specific argument.
How do you override a CSS style you cannot edit?
To override a CSS style, first inspect why it wins in browser developer tools. For normal rules in the same origin and layer, use a matching selector with greater specificity, or equal specificity later in the stylesheet when scope also ties. A later layer helps only against earlier layered normal rules; it cannot beat unlayered normal styles.
Is specificity calculated per rule or per declaration?
CSS specificity is calculated for a selector, while the cascade chooses a winning declaration separately for each property. An element can therefore get its color from one rule and its padding from another.
Sources
-
[1]
Specificity(developer.mozilla.org)
-
[2]
Introducing the CSS Cascade(developer.mozilla.org)
-
[3]
ID selectors(developer.mozilla.org)
Read Next
Learn CSS typography and color through color formats, length units, font and text properties, custom properties, and shadows.
How CSS selectors target elements: type, class, and id selectors, combinators, pseudo-classes, and how specificity decides which rule wins.
CSS pseudo-classes like :hover, :focus, and :nth-child(): how they match elements by state and position without extra classes in your markup.