Two selectors target the same element with conflicting rules but equal specificity. Which one wins and why?
Advertisement
🧩 Scenario
Architecture Walkthrough
Source Order Is the Final Tiebreaker
When two declarations conflict and everything above specificity has already tied, the browser applies the one that appears last in source order. Not the first, and not the more "specific-looking" one. Last wins.
So given .btn { color: blue } followed by .primary { color: red }, an element matching both renders red, because .primary comes later in the stylesheet. Both selectors are (0, 1, 0), so specificity cannot break the tie and the cascade falls through to document order.
The Order of Classes in the HTML Is Irrelevant
This is the misconception worth stamping out first. class="primary btn" and class="btn primary" produce identical results. The class attribute is an unordered set of tokens; it expresses which rules match, not which rule wins.
The consequence is significant for utility-first CSS. In Tailwind, writing class="text-red-500 text-blue-500" does not give you blue because blue is listed second. You get whichever of the two rules Tailwind's generated stylesheet emits later, which is determined by its internal ordering, not by your markup. This is exactly why utility frameworks ship conflict-resolution helpers such as tailwind-merge: the cascade cannot resolve the intent, so the class list has to be deduplicated before it reaches the DOM.
Source Order Means Bundle Order
In a real project, "source order" almost never means the order you see in the file you are editing. It means the order of the declarations in the final concatenated CSS the browser parses.
That order is set by your build pipeline: the sequence of @import statements in the entry stylesheet, the order in which JavaScript modules import their CSS, how a bundler hoists or splits chunks, and where a CSS-in-JS runtime injects its <style> tags. Two rules in two different files have a well-defined relative order only after the bundler decides it. This is the mechanism behind one of the more frustrating bug reports in frontend work: a component's appearance changes after a refactor that touched no CSS at all, because an import moved and one rule now lands after another.
It is also why @layer is such a meaningful addition. Layers let you declare precedence explicitly and once, at the top of your entry file, so that the winner no longer depends on bundler behaviour you do not fully control.
What Could Have Pre-empted the Tie
Before concluding "they tie, so last wins," verify that the earlier cascade steps genuinely tied. Origin and importance are resolved first, so an !important on either rule ends the comparison immediately. Cascade layers come next, so if the two rules live in different @layers, layer order decides regardless of source position. Inline styles are resolved after layers and before specificity, so a style attribute defeats both rules.
There is also a subtler case that looks like a source-order surprise but is not: shorthand versus longhand. A later background: linear-gradient(...) resets background-color to its initial value as part of expanding the shorthand, even though the two declarations name different properties. The same happens with font resetting font-weight, or border resetting border-color. The conflict is real but it is happening at the longhand level after shorthand expansion, not through any specificity or ordering exception.
Key Code Explained
/* Equal specificity (0,1,0) -> the LAST rule wins */
.btn {
color: blue;
}
.primary {
color: red; /* applies */
}
/* <button class="primary btn"> and <button class="btn primary">
both render RED. The class attribute order is irrelevant. */
/* Identical selector, later declaration wins */
.badge {
color: navy;
}
.badge {
color: crimson; /* applies */
}
/* An earlier cascade step pre-empts the tie entirely */
@layer base, components;
@layer components {
.card {
padding: 8px; /* applies: later layer wins regardless of source order */
}
}
@layer base {
.card {
padding: 24px;
}
}
/* !important is resolved before specificity or source order */
.first {
color: green !important; /* applies even though .second comes later */
}
.second {
color: purple;
}
/* Shorthand resets longhands, which LOOKS like a source-order oddity */
.panel {
background-color: red;
}
.panel {
background: linear-gradient(#fff, #eee);
/* background-color is reset to transparent by the shorthand expansion */
}
/* Reverse it and the red survives, because background-color is set last */
.panel-fixed {
background: linear-gradient(#fff, #eee);
background-color: red; /* wins for that one longhand */
}
The @layer block is the most instructive part. The base layer rule appears later in the file yet loses, because layer order is resolved before source order is ever considered. It is the clearest demonstration that "last wins" is the final fallback, not a general rule.
The .panel pair is the other one to remember. Nothing about specificity or ordering is unusual there, but the symptom, a colour vanishing when an unrelated-looking property is added, sends people hunting for cascade exceptions that do not exist. The real mechanism is shorthand expansion writing to every longhand it owns, including the ones you did not mention.
Tradeoffs
| Cascade step | Resolved when | Beats source order? |
|---|---|---|
Origin + !important | First | Yes |
Cascade layers (@layer) | Second | Yes |
Inline style attribute | Third | Yes |
| Specificity tuple | Fourth | Yes |
| Source order | Last | It is the last resort |
HTML class attribute order | Never consulted | No effect at all |
What Interviewers Actually Check
- Whether you say the last rule wins rather than the first
- Whether you know the HTML
classattribute order has no bearing on the outcome - Whether you connect source order to bundle order rather than to the file you are editing
- Whether you check that the earlier cascade steps genuinely tied before invoking source order
- Whether you can recognise shorthand-resets-longhand as a distinct mechanism rather than a cascade exception
Follow-Up Questions
- How does
tailwind-mergeresolve conflicting utility classes, and why can the cascade not do that job itself? - If a CSS-in-JS library injects
<style>tags at runtime, where in source order do those declarations land relative to your static stylesheet? - How would you use
@layerto make a third-party stylesheet reliably overridable without touching specificity? - Two identical declarations differ only in that one is inside a
@mediaquery that currently matches. Which applies, and does the media query affect specificity? - How do CSS Modules or scoped styles change the reasoning here, given that they rewrite class names at build time?
Common Candidate Mistakes
- Claiming the class listed later in the HTML
classattribute wins, when the attribute is an unordered token set and never influences the cascade - Saying the first matching rule wins, which inverts the actual behaviour on an exact tie
- Reasoning about source order from the file on screen instead of the bundled output, and therefore missing bugs caused by import order, chunk splitting, or runtime style injection
- Missing that a later shorthand resets longhands it owns, so adding
backgroundsilently clears a previously setbackground-color - Jumping straight to "they tie, so last wins" without first confirming that importance, layer order, inline styles, and specificity all actually tied
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you recite the cascade steps in order from origin and importance through to source order?
- Can you explain why the last declaration wins on an exact tie?
- Can you explain why the HTML
classattribute order has no effect? - Can you describe how bundler import order determines the practical winner?
- Can you explain how a later shorthand can silently reset an earlier longhand?
Summary
When two declarations conflict at equal specificity, equal importance, the same origin, and the same cascade layer, the one that appears last in source order applies. The order of class names in the HTML class attribute is never consulted, which is why utility-first frameworks need dedicated merge helpers instead of relying on markup order to express intent.
In practice, source order means the order of declarations in the bundled stylesheet the browser parses, not the order in the file you are editing. Import sequence, chunk splitting, and runtime style injection all determine it, which is how a component's appearance can change after a refactor that touched no CSS. @layer is the antidote: it declares precedence explicitly and once, so the winner stops depending on build behaviour.
Before concluding that source order decided a conflict, confirm the earlier steps tied. Origin and !important are resolved first, then cascade layers, then inline styles, then specificity. And watch for shorthand expansion, where a later background or font declaration resets longhands you set earlier. That looks like a cascade exception but is simply the shorthand writing to every longhand it owns.
Does the order of classes in the HTML class attribute matter?
No. class="b a" and class="a b" resolve identically. Only the order of the rules in the stylesheet matters, which is why utility frameworks cannot resolve conflicts by attribute order.
What if the two rules are in different files?
What matters is their final order in the concatenated output the browser sees, which is determined by import order in your bundler, not by filename or directory.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement