This article is published in English.
How the Cascade Picks a Winner: Importance, Specificity and Source Order
See how browsers resolve competing CSS declarations, how to read specificity as a four-part comparison, and why hover rules and !important so often surprise you.
When several CSS rules target the same element and set the same property, the browser cannot apply all of them. It needs a deterministic way to pick exactly one declaration, and that process explains a whole class of "why is my style ignored?" bugs. By the end of this guide you will be able to compute a selector's specificity, predict which declaration wins, and debug stubborn cases such as a :hover rule that never fires, without reaching for !important.
Where this fits in the rendering pipeline
At a high level, the browser turns markup and styles into pixels through a series of stages. HTML is parsed into the DOM, CSS into the CSSOM, the two are combined into a render tree, and then layout and paint produce what you see:
HTML
↓
DOM
↓
CSS
↓
CSSOM
↓
Render Tree
↓
Layout
↓
Paint
↓
Pixels
Hidden inside the CSS step are three related questions. First, when multiple declarations compete, which one wins? Second, once a winner is chosen, what does its value actually resolve to? Third, what happens when an element has no value for a property at all? The answers are the cascade (with specificity at its core), value processing and inheritance. They are often taught as unrelated topics, but inside the browser they are consecutive steps of the same job. This guide focuses on the first question. If you want more context on what happens after styles are resolved, see how the browser paints and where React fits in.
Rules, declarations and competing values
A little vocabulary first. A CSS rule is made of a selector followed by a declaration block:
.button {
background-color: blue;
}
In that rule, .button is the selector, background-color: blue; is a declaration, background-color is the property and blue is the value. The value exactly as you wrote it is called the declared value.
A real stylesheet will often contain several declarations for the same property on the same element. One rule might target every button:
button {
background-color: red;
}
while others, maybe in a different file, target buttons in general and one specific button by its id:
button {
background-color: blue;
}
#submit {
background-color: green;
}
If a single <button id="submit"> matches all three, which background colour should it get? Settling that is the job of the cascade. It resolves conflicts by looking at the importance of each declaration, the specificity of its selector, and the order in which declarations appear. Once importance has been taken into account, specificity is usually what decides the outcome.
The full cascade in the specification also considers the origin of a stylesheet (browser defaults, user styles, author styles) and, in modern CSS, cascade layers declared with @layer. For everyday author stylesheets without layers, importance, specificity and source order are the three levels you will be reasoning about.
What specificity measures
Specificity is the browser's measure of how precisely a selector targets an element. Selectors do not all carry the same weight. Compare a type selector:
p {
color: red;
}
a class selector:
.text {
color: blue;
}
and an id selector:
#title {
color: green;
}
If all three match the same element, the browser ranks them using a fixed hierarchy of selector kinds, from strongest to weakest:
Inline styles
↓
IDs
↓
Classes / pseudo-classes / attributes
↓
Elements / pseudo-elements
When the competing declarations have equal importance, the more specific selector wins. Here the id rule would win and the text would be green.
A precise note on the top row: inline styles set through the style attribute are not selectors, and the current specification treats them as a separate step that beats any selector-based author declaration. Modelling them as the highest "column" of specificity, as is commonly done, gives the same result in practice, so the rest of this guide keeps that convenient model.
Reading specificity as four columns
The most common misconception is that specificity is a single score you can add up. It is better understood as a tuple of four counts, one per category:
Inline | IDs | Classes | Elements
For a given selector, you count how many parts fall into each category. A single class selector is the simplest case:
.button {
background: blue;
}
It contains no inline style, no id, one class and no elements:
Inline styles → 0
IDs → 0
Classes → 1
Elements → 0
which you can write compactly as:
0, 0, 1, 0
Now take a more elaborate selector:
nav#main .button div {
background: green;
}
It contains one id (#main), one class (.button) and two type selectors (nav and div), so its specificity is:
0, 1, 1, 2
To compare two selectors, the browser reads these tuples from left to right, starting with the most significant column. The first column where they differ decides the result, and the columns to its right no longer matter. That is why a single id outweighs any number of classes, and a single class outweighs any number of type selectors: there is no carrying over from one column to the next, so ten classes never "add up" to an id. You do not need to memorise arithmetic; you need to remember the comparison order.
Working through a real conflict
Consider a button with both a class and an id. Note that this is plain HTML markup, so it uses class rather than JSX's className:
<button class="button" id="submit">
Don't Click
</button>
Now suppose the stylesheet contains a class rule:
.button {
background: blue;
}
plus several others, including a type selector, a long descendant selector and an id-plus-class rule with a hover state:
button {
background: purple;
}
nav#main .button div {
background: green;
}
#submit.button:hover {
background: yellow;
}
All of them declare background, so they compete. The browser does not simply take whichever appears last; it compares specificity first.
One detail is easy to miss in that block: nav#main .button div actually targets a div nested inside an element with class button, not the button itself. Because the subject of a selector is its rightmost part, this rule never matches our <button>, whatever its specificity. Checking that a rule matches at all is always step zero of debugging.
For the rules that do match, compare a class selector:
.button
with a bare type selector:
button
The first has one class, the second only one element. So:
.button
outranks:
button
and the button is blue, not purple, even though the purple rule appears later.
Add an id to the mix:
#submit.button
The id places this selector in a higher column than anything built only from classes and types. Comparing left to right, the id column settles the contest immediately, which is why one id can beat a selector made of many classes.
Why a correct :hover rule can do nothing
This case causes a lot of confusion during debugging. Start with a base rule for the button:
#submit.button {
background: red;
}
and a hover rule for the same element:
#submit.button:hover {
background: yellow;
}
The hover rule has one id, one class and one pseudo-class, which gives it more specificity than the base rule, so hovering turns the button yellow as expected.
Now imagine that another part of the codebase styles the same button through a much longer selector:
nav#main div#container #submit.button {
background: red;
}
while the hover rule stays unchanged:
#submit.button:hover {
background: yellow;
}
The hover rule still includes a pseudo-class:
:hover
and pseudo-classes do count in the class column. But that only adds one class-level point. The long selector contains three ids against the hover rule's one, so it wins in the id column before classes are even compared. The result is a :hover rule that is syntactically perfect, matches the element, and still changes nothing on screen.
The lesson is that when interaction states seem broken, the pseudo-class is rarely the culprit. The real issue is usually that some other declaration is more specific. Browser developer tools make this visible: the Styles panel lists every matching rule and strikes through the declarations that lost, which tells you exactly which selector is beating yours.
Ties are broken by source order
Sometimes two selectors have identical specificity. Take two rules with the same class selector:
.button {
background: red;
}
.button {
background: blue;
}
They are equally specific and equally important, so neither of the first two levels can decide. The browser then falls back to source order: the declaration that appears later wins. Given this order:
.button {
background: red;
}
.button {
background: blue;
}
the button ends up blue.
The whole decision process can be pictured as a sequence of tie-breakers:
Importance
↓
Specificity
↓
Source Order
Each level is only consulted if the previous one could not produce a winner. Importance comes first; if that ties, specificity decides; if that also ties, the last declaration wins.
The cost of reaching for !important
Almost every developer has used this escape hatch at least once:
color: red !important;
Adding !important raises the importance of a declaration. Because importance is checked before specificity, a declaration marked this way can beat another declaration with far greater specificity. For instance:
.button {
background: purple !important;
}
will win over a normal declaration on a long, id-heavy selector. When two !important declarations compete, the browser goes back to comparing their specificity and then their order.
That power is precisely what makes it risky. A familiar debugging spiral looks like this:
"My style isn't working."
↓
"Let's increase the specificity."
↓
"Still not working."
↓
"Let's add !important."
↓
"It works!"
The style finally shows up, but the underlying conflict has not gone away; it has been pushed to the next person who needs to override that property and now has to fight an !important of their own. As more of these accumulate, the stylesheet gets harder and harder to reason about. Treat !important as a last resort, and read a sudden need for it as a signal that the CSS probably wants refactoring.
Before you type:
!important
ask a more useful question: why is my declaration not winning? Then check the levels in order:
- Is a competing declaration more important?
- Is a competing selector more specific?
- Does a competing declaration come later in the source?
Legitimate uses do exist, such as utility classes that are meant to always apply or overriding inline styles injected by a third-party widget you cannot change, but they should be deliberate rather than reflexive.
Writing selectors that win naturally
There is a broader maintainability point here. When a style does not apply, it is tempting to make the selector longer and longer until it does:
body div section nav ul li a.button {
color: red;
}
It works, but every extra part raises the bar for any future override, couples the style to a specific DOM structure, and makes the stylesheet harder to read. Instead of asking how to make a selector win at any cost, ask how to organise the CSS so that the intended declaration wins on its own. Keeping most selectors to a single class, avoiding ids for styling, and grouping more specific overrides close to the rules they modify all help.
This matters most in large codebases where many people write CSS. Specificity is meant to make results predictable, not to become an arms race between selectors.
Using source order with third-party stylesheets
Source order becomes a practical tool when you combine your own styles with a reset or a third-party stylesheet. Those files typically set styles for common elements, and you want your rules to override them. Loading your stylesheet after theirs makes that straightforward:
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="style.css">
When your selectors have the same specificity as the library's, the later file wins, so putting style.css after reset.css lets your declarations take effect without inflating selectors.
Relying on order has a cost too. If someone later reorders the <link> tags or the imports in a bundler entry point, styles can silently flip. Where you can, prefer rules whose specificity makes the intended winner clear, and use source order as the final tie-breaker it was designed to be. Cascade layers, where your target browsers support them, are a more explicit way to express "the library comes first, our styles come after".
After the winner: the cascaded value
At this point the first question is answered. The browser collected every declaration for the property, weighed importance, then specificity, then source order, and settled on one. That winning value is called the cascaded value.
The work is not quite done, though. Suppose the winning declaration is:
width: 66%;
A percentage like this cannot be painted directly; the browser still has to process it, resolving it against the containing block to get an actual length. That value processing, together with inheritance for properties that have no declaration at all, is the next stage of turning CSS into pixels.
Key takeaways
- The cascade resolves conflicts in a fixed order: importance, then specificity, then source order.
- Specificity is a four-column comparison (inline, ids, classes/pseudo-classes/attributes, elements/pseudo-elements) read left to right; lower columns never carry over into higher ones.
- Confirm a rule actually matches the element before comparing its specificity; the rightmost part of a selector is what it targets.
- A
:hoveror other pseudo-class rule adds only class-level weight and can lose to a more specific base rule. !importantwins by changing importance, not by fixing the conflict; use it deliberately and sparingly.- Favour short, class-based selectors and a sensible stylesheet order so the intended declaration wins without escalation.