skip to content

Two CSS rules with identical specificity set the same property on one element. Which declaration wins, and what determines that order in a bundled stylesheet?

level: seniorimportance: should knowfreq 40%

answer

  1. last one standing, literally
  2. the browser sees one flattened order
  3. links, imports, then concatenation
  4. injected styles land at the end
  5. reorder rather than inflate

basics

~20 s

The declaration that comes later wins. Order means position in the document's final flattened stylesheet — the order stylesheets are linked, imported or injected, and the order files are concatenated inside a bundle — not the order of files in your editor.

solid answer

~40 s

When the specificity triples are identical, the cascade falls through to source order and the last declaration wins. The subtlety in a real app is what "last" means: the browser sees one flattened sequence built from the order of `<link>` and `<style>` elements in the document, the position of each `@import` inside a sheet, and the order your bundler concatenated files into each of those sheets. So an override can be perfectly targeted and still lose because its file happens to be bundled first. A `<style>` element injected at runtime at the end of `<head>` wins ties against everything linked earlier for the same reason. The mechanism-correct fix is to move the override later, not to inflate its selector or reach for `!important`.

code

css · 4 lines
css
.button { background: blue; }
.button { background: green; }

/* Both score 0-1-0, so the later declaration applies: green. */

go deeper

for a junior

Remember the basic rule: with equal specificity, the rule written later wins. Moving a declaration below the one it should beat is the everyday fix.

for a middle

Explain what the browser actually orders — the flattened sequence built from link and style elements, @import positions and bundle concatenation — rather than the files as you see them on disk.

for a senior

Diagnose an order bug end to end: confirm the triples really are equal, check layer and origin first, locate both rules in the real load order, then fix by ordering instead of escalating.

for a principal

Own the load-order contract itself. Decide how much of the codebase's override behaviour is allowed to depend on concatenation order versus being declared explicitly, and make that ordering a reviewable part of the build.

## The rule Source order is the cascade's final tiebreak. After origin and importance, after cascade layers if any are used, and after the specificity triples have been compared column by column, if the candidates are still tied then the declaration that appears later wins. That is why the classic "just move it below" fix works, and why a duplicated rule at the bottom of a file silently defeats the one at the top. ```css .button { background: blue; } .button { background: green; } /* wins — same 0-1-0, appears later */ ``` ## What "later" actually means The browser does not reason about files. It builds one ordered sequence of style rules for the document and asks where each declaration sits in it. That sequence comes from, in order: 1. **Document order of the stylesheet-bearing elements** — every `<link rel="stylesheet">` and every `<style>` element, in the order they appear in the markup. A sheet linked in the `<head>` comes before one linked at the end of `<body>`. 2. **`@import` position within a sheet.** An imported sheet's rules take the place of the `@import` rule itself. Since `@import` must appear before any style rules in the sheet (following only `@charset` and `@layer` statements), imported rules land ahead of the importing file's own rules. 3. **Concatenation order inside each sheet.** If a build step merges twenty source files into one stylesheet, the merge order *is* the source order for every rule in them. 4. **Runtime insertion.** A `<style>` element created and appended after load takes the position of the element it was appended at — usually the end of `<head>`, which puts it after everything linked earlier. This is where the surprises live. Developers reason about the file they are editing and forget that the file's position was chosen by an import graph or a glob pattern in a build config. ## The diagnosis When an override with a correct, matching, equal-specificity selector does not take effect, work through it in this order: - **Are the triples actually equal?** A single extra class in the other rule ends the discussion before source order is reached; recount both selectors rather than assuming. - **Are they in the same layer?** If cascade layers are used, layer order is decided before specificity and before source order, so a rule in a later-declared layer wins whatever its position in the file. - **Where do the two files sit in the flattened order?** Check the actual link/import/bundle order, not the directory listing. - **Is the winner even a stylesheet rule?** If it came from the `style` attribute, source order is irrelevant. ## Why the mechanism-correct fix is ordering There are three ways to make a tied rule win, and they are not equivalent. You can move it later, which changes nothing about how either selector scores and leaves both rules readable. You can inflate the selector — duplicate a class, add an ID — which wins by raising specificity and permanently raises the bar for anyone overriding it later. Or you can add `!important`, which wins by skipping to an earlier cascade step and raises that bar even higher. Ordering is the only one of the three that does not leave a ratchet behind. This is why the load order of a stylesheet is a real architectural decision: resets first, then base element styles, then components, then the narrow overrides — an order chosen precisely so that the rules you expect to win are already last, and equal-specificity conflicts resolve the way a reader would guess. ## The flip side: order dependence is fragile A stylesheet whose correctness depends on subtle file ordering breaks the day someone reorders an import or a bundler changes its traversal. That fragility is the honest argument against relying on ordering as your main override tool at scale — the reason cascade layers exist is to make "this group of rules loses to that group" an explicit, declared fact rather than an emergent property of concatenation order. Naming that tradeoff, rather than just reciting "last one wins", is what distinguishes a senior answer here.

  • Where must an @import rule appear in a stylesheet, and how does it affect source order?
    It must come before any style rules, preceded only by `@charset` and `@layer` statements. The imported sheet's rules take the position of the `@import` itself, so everything imported sits ahead of the importing file's own rules — which is why an override written at the bottom of the entry file beats the imported partials at equal specificity.
  • Does moving a <link> element earlier in the head change which rule wins?
    For equal-specificity conflicts, yes — the document order of stylesheet-bearing elements sets the flattened source order, so a sheet linked earlier now loses ties it used to win. It changes nothing where the triples differ, since specificity is compared before source order is ever consulted.
  • A rule appears later in the file and still loses. What are the likely explanations?
    Either the triples are not actually equal — recount, since one extra class settles it before order matters — or the winning declaration is not a peer: it may be `!important`, it may come from the `style` attribute, or the two rules may sit in different cascade layers, where layer order is decided before both specificity and source order.

saying these in an interview costs you the question

  • Says the first matching rule wins
  • Assumes editor file order equals browser source order
  • Reaches for !important instead of fixing load order
  • Thinks a linked stylesheet always outranks a <style> element
  • Forgets that layer order is decided before source order

context