skip to content

In a mobile-first CSS file, why must min-width media query blocks appear in ascending order of their width values?

level: middleimportance: should knowfreq 50%

answer

  1. min-width conditions overlap, they do not band
  2. several blocks match at once
  3. at-rules contribute no specificity
  4. last matching declaration wins
  5. max-width is the mirror: descending

basics

~20 s

At a wide viewport every min-width query with a smaller threshold matches simultaneously. Media queries add no specificity, so among equally specific matches the last one in source order wins — which means the widest breakpoint must be written last.

solid answer

~50 s

`min-width` conditions are open-ended, so they stack: at 1200px a `min-width: 40rem` block and a `min-width: 64rem` block both match. The at-rule contributes nothing to specificity, so if both set the same property on the same selector, the cascade falls through to source order and the later block wins. Write them ascending and the widest matching breakpoint is naturally last, which is what you want — each wider block refines the one before it. Write them descending and the 40rem block lands after the 64rem block and silently overrides it, so the layout appears to "stop responding" above the first breakpoint. The mirror rule applies to a desktop-first sheet: `max-width` blocks stack downward, so they go in descending order. A preprocessor or a tooling step that sorts blocks by threshold makes this mechanical rather than a thing you remember.

code

css · 5 lines
css
.grid { display: grid; grid-template-columns: 1fr; }

@media (min-width: 64rem) { .grid { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 48rem) { .grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 30rem) { .grid { grid-template-columns: repeat(2, 1fr); } }

go deeper

for a junior

Know that a min-width query means "this width and everything wider", so several can be true at once. Say that the blocks go narrowest to widest and you will have the practical rule right.

for a middle

Walk the cascade explicitly: equal specificity because at-rules add none, therefore source order decides, therefore the widest must be last. Be ready to state the mirrored rule for a max-width sheet.

for a senior

Describe the symptom as you would meet it — a layout that stops responding past the first breakpoint, with struck-through declarations from a matching query — and reject the !important fix as a specificity escalation you would have to unwind later.

for a principal

Treat it as something the codebase should make impossible: one query direction enforced, breakpoints kept beside the component they modify, and any build step that merges or reorders media queries verified rather than trusted.

## Min-width conditions are open-ended, not bands `@media (min-width: 40rem)` does not mean "between 40rem and the next breakpoint". It means "40rem or wider, forever". So at a 1200px viewport with a 16px root default, all of these match at once: ```css @media (min-width: 30rem) { .grid { grid-template-columns: repeat(2, 1fr); } } @media (min-width: 48rem) { .grid { grid-template-columns: repeat(3, 1fr); } } @media (min-width: 64rem) { .grid { grid-template-columns: repeat(4, 1fr); } } ``` Three declarations for `grid-template-columns` on `.grid` are all live simultaneously. Exactly one of them can win. ## How the cascade picks the winner The cascade resolves conflicting declarations in a fixed order: origin and importance, then cascade layers, then **specificity**, then **order of appearance**. Specificity is computed from the selector — `.grid` is one class, full stop. Wrapping the rule in `@media` changes nothing about it; an at-rule is not part of the selector and contributes no specificity units. So all three declarations tie on specificity, and the tiebreak is the last one to appear in the document. In the ascending listing above, the last *matching* block at 1200px is the 64rem one, and four columns win. That is the intended reading: **each wider block refines the previous one**, exactly like the base rules being refined by the first query. ## What goes wrong descending Reverse the listing: ```css @media (min-width: 64rem) { .grid { grid-template-columns: repeat(4, 1fr); } } @media (min-width: 48rem) { .grid { grid-template-columns: repeat(3, 1fr); } } @media (min-width: 30rem) { .grid { grid-template-columns: repeat(2, 1fr); } } ``` At 1200px all three still match, but now the 30rem block is last, so the grid renders two columns at every width from 30rem up. The 48rem and 64rem blocks are dead code that never wins. The symptom in the browser is distinctive: the layout responds correctly up to the first breakpoint and then appears frozen, with DevTools showing the wider declarations struck through. Nothing is broken syntactically, no warning is emitted, and the file looks organised — which is why this costs people an afternoon. ## The mirror rule for max-width Desktop-first sheets have the same problem inverted. `max-width: 64rem` means "64rem or narrower, down to zero", so at 400px every max-width block matches and the **last** one still wins. To make the narrowest block the effective one, `max-width` queries must be written in **descending** order. One sentence covers both: *order your breakpoints so that the most specific-to-the-current-viewport block is the last one that matches.* ## Why the tie-break is not specificity you can adjust A tempting bad fix is to give the wider block a heavier selector — `body .grid`, or `!important`. That does work mechanically, and it is a trap: you have converted an ordering problem into a specificity escalation that the next author has to out-specify again. The same applies to leaning on cascade layers to force the outcome. Ordering costs nothing and leaves the selectors clean. ## Making it not a memory problem Three things reduce this to a non-issue in a real codebase. Keep every breakpoint for a component **adjacent** to that component's rules rather than in a global "responsive" section at the bottom of the file, so the ordering is visible in one screenful. Use one direction consistently across the codebase, so nobody has to work out which way the current file leans. And if breakpoints are generated by a build step, sort them by threshold there — several toolchains sort media queries when they merge them, though merging can itself change relative order, so verify rather than assume. ## The interview version of the answer One sentence of mechanism — min-width conditions overlap, at-rules add no specificity, later wins — followed by the symptom you would recognise in the wild. That combination is what separates someone who has debugged it from someone who has read about it.

  • What order do max-width blocks need, and why?
    Descending. A `max-width` condition is open-ended downward — `max-width: 64rem` matches everything from 64rem down to zero — so at a narrow viewport all of them match at once. Since the tie is still broken by source order, the narrowest block has to come last to win. The general rule covering both directions is: order the blocks so the one most specific to the current viewport is the last that matches.
  • Could you fix a descending stylesheet by adding !important to the wider breakpoints instead of reordering?
    It would render correctly, and I would not do it. You have replaced a free fix with a specificity escalation that the next person has to out-specify, and it spreads: once one breakpoint is important, the overrides around it have to be too. Reordering costs nothing, leaves selectors clean, and fixes the cause rather than the symptom. The same objection applies to bumping the selector weight or forcing the outcome through a cascade layer.
  • How would you recognise this bug in the browser rather than by reading the file?
    The layout responds correctly up to the first breakpoint and then looks frozen as you keep widening. Inspect the element and the wider breakpoint's declaration will be there but struck through, with the narrower block's declaration winning below it. Struck-through declarations from a *matching* media query — rather than a greyed-out non-matching one — is the specific tell that the conflict is ordering, not a condition that never fires.

saying these in an interview costs you the question

  • Thinks min-width blocks define non-overlapping bands
  • Believes a wider breakpoint automatically outranks a narrower one
  • Reaches for !important instead of reordering the blocks
  • Says the media condition contributes to specificity
  • Assumes a non-matching query is the cause when the rule is struck through

context