Topic 9.2
Media Queries
In one line
A media query wraps rules in a condition about the device, such as @media (min-width: 40rem) { ... }, so they apply only when it's true; you can test width ranges with min-/max- or the newer range syntax, test orientation, and combine tests with and, commas and not.
Think of it like this
Clothes for the weather. Your parent's note says "if it's colder than 15 degrees, wear a jacket; if it's raining, take an umbrella". The jacket rule only applies on cold days, the umbrella rule only on rainy days, and on a cold rainy day both apply. A media query is that kind of "if" for CSS: the rules inside it only apply when the condition about the screen is true.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Media query
- A condition about the device or viewport, like
(min-width: 600px). Rules inside it apply only when it is true. - At-rule
- A CSS statement that starts with
@, like@media,@importor@container. - Media feature
- One thing you can test in a media query, written in parentheses:
width,orientation,hover… - Media type
- The broad kind of output:
screen,printorall. - min-width / max-width
- "At least this wide" / "at most this wide". Both include the exact value.
- Range syntax
- The newer way to write width tests with signs:
(width >= 600px),(400px <= width < 900px). - Orientation
portraitif the viewport is at least as tall as wide,landscapeif it is wider than tall.- matchMedia
- A JavaScript function that checks a media query and tells you if it matches, and when that changes.
Step by step
01The shape of a media query
An @media at-rule, a condition, then normal rules inside braces. This one changes the page background on screens at least 600px wide.
@media (min-width: 600px) {
body {
background: lavender;
}
}02min-width for mobile first
Write the base first, then each min-width query after it, from small to large. At 1000px all three blocks match, and because each is later, the last matching one wins.
.grid { grid-template-columns: 1fr; }
@media (min-width: 40rem) {
.grid { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 60rem) {
.grid { grid-template-columns: 1fr 1fr 1fr; }
}03The same thing with range syntax
Range syntax reads like maths. >= is the same as min-width. You can also target only a band of widths, which is handy for one-off fixes.
@media (width >= 40rem) {
.grid { grid-template-columns: 1fr 1fr; }
}
/* only tablets-ish widths */
@media (40rem <= width < 60rem) {
.sidebar { display: none; }
}04Combine conditions
and needs both. A comma means either. not flips a whole query.
/* wide AND landscape */
@media (min-width: 40rem) and (orientation: landscape) {
.hero { min-height: 60vh; }
}
/* small screens OR printing */
@media (max-width: 30rem), print {
.ads { display: none; }
}
/* anything except print */
@media not print {
.print-only { display: none; }
}05How the browser evaluates it
The browser keeps checking every media query. When one starts or stops matching (resize, rotate, zoom), it adds or removes those rules and recalculates styles.
06Use the same query in JavaScript
matchMedia returns a MediaQueryList. Read .matches now, and listen for change to react later, for example to swap a menu between a drawer and a bar. Don't poll innerWidth in a resize handler for this.
const wide = window.matchMedia("(min-width: 40rem)");
console.log(wide.matches); // true or false right now
wide.addEventListener("change", (e) => {
console.log("wide now?", e.matches);
});Try it yourself
- 1
Add a third condition
In the first example, add a block
@media (orientation: landscape) { .grid > div { border: 2px solid #333; } }. Predict: does the narrow preview pane count as landscape? (Usually yes: it's wider than tall.) Run it and check. - 2
Predict matchMedia
In the second example, add
q("not all")andq("(min-width: 0px) and print"). Predict both answers before you run. (Both arefalse.) - 3
Fix the order
In the last example, move
.b { color: gray; }above its media rule. Predict the new console line forb, then run. It should becomergb(0, 128, 0).
Code & diagrams
The label is generated by CSS, so it always tells you which query is winning. Open the full-screen editor and resize the window to watch it change at 480px and 800px.
Preview
Console
Nothing logged yet.These lines give the same answer on every screen, so they are safe to print. An invalid query doesn't throw: it simply never matches. That's also what happens to a CSS `@media` block with a typo: its rules silently never apply.
Console
(nothing was printed)Expected console output
all -> true
screen -> true
print -> false
(min-width: 0px) -> true
(width >= 100000px) -> false
not print -> true
print, (min-width: 0px) -> true
invalid query matches? falseEach `@media` block is a `CSSMediaRule` in the CSS Object Model, holding its own list of rules. DevTools shows the same structure in the Styles pane, with the query written above the rules it controls.
Preview
Console
Nothing logged yet.Expected console output
@media screen and (min-width: 600px) | rules inside: 1
@media (600px <= width < 900px) | rules inside: 1
@media (orientation: landscape), print | rules inside: 1`(min-width: 0px)` always matches, so both media rules apply. For `.a` the media rule is later and wins. For `.b` the plain rule is later and wins: a media query gives no extra strength.
Preview
Console
Nothing logged yet.Expected console output
a: rgb(0, 128, 0)
b: rgb(128, 128, 128)Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget the parentheses or the unit
Write @media min-width: 600px { ... } or @media (min-width: 600) { ... }.
Break #2
The 599.5px gap
Use @media (max-width: 599px) for the mobile menu and @media (min-width: 600px) for the desktop menu, then zoom the browser to 110% and resize slowly.
Myth vs fact
Myth
Rules inside a media query are more important than rules outside.
Fact
Media queries only switch rules on and off. Inside, normal specificity and order apply, so a later plain rule beats an earlier media rule with the same selector.
Myth
orientation: portrait means the phone is held upright.
Fact
It compares the viewport's height and width. A desktop window that's taller than wide is portrait; a phone with the keyboard open may become landscape.
Myth
rem in a media query follows the font size I set on html.
Fact
In media queries, rem and em use the browser's initial font size (the user's setting, normally 16px), because the query is evaluated before any element styles exist.
Myth
You need only screen and before every query.
Fact
only existed to hide queries from browsers older than 2010. all is the default media type, so @media (min-width: 40rem) is enough.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
<link rel="stylesheet" media="print" href="print.css">is still downloaded, but at low priority and without blocking rendering, because the browser knows it doesn't apply to the screen now. That's why non-matching media attributes are a classic way to load non-critical CSS. - ▸
Range syntax (
width >= 600px) is Media Queries Level 4 and has been Baseline since 2023 (Safari 16.4 was the last to ship). If you must support older browsers, PostCSS can compile it tomin-width. - ▸
Media queries measure the viewport, so a component placed in a narrow sidebar on a wide screen still gets the "wide" styles. That limit is why container queries exist (Topic 9.6). Use media queries for page-level layout, container queries for components.
- ▸
Other useful media features:
resolution(e.g.(min-resolution: 2dppx)for high-density screens),aspect-ratio,display-mode: standalone(an installed web app) and the preference features in Topic 9.3.device-widthis deprecated; usewidth.
Remember this
- 1
A media query is a condition about the media: the device or environment showing the page (its viewport width, orientation, whether it is a screen or a printer, and more). You write it as an at-rule (a CSS statement that starts with
@):@media (min-width: 40rem) { .cards { grid-template-columns: 1fr 1fr; } }. The rules inside the braces join the cascade only while the condition is true, and the browser re-checks it whenever the window is resized or rotated. - 2
Each test in parentheses is a media feature. The most used is
width(the viewport width), usually with a prefix:(min-width: 600px)means "width is 600px or more", and(max-width: 599px)means "width is 599px or less". Both are inclusive: at exactly 600px,min-width: 600pxis true. There is alsoheight,min-heightandmax-height, and **orientation**:portraitwhen the viewport is at least as tall as it is wide,landscapewhen it's wider than tall. Note that orientation is about the viewport, not the device: a desktop window that is taller than wide isportrait. - 3
The newer range syntax (Media Queries Level 4) uses comparison signs:
(width >= 600px),(width < 600px), and a range in one test:(600px <= width < 900px). It is shorter and fixes a real bug: withmax-width: 599pxandmin-width: 600px, a viewport of 599.5px (possible with zoom or a high-density screen) matches neither. With(width < 600px)and(width >= 600px)there is no gap. It works in all major browsers since 2023. - 4
You can combine tests.
andmeans both must be true:@media (min-width: 600px) and (orientation: landscape). A comma means "or":@media (max-width: 400px), printapplies on small screens or when printing (Level 4 also allows the wordorinside a condition).notnegates a whole query:@media not print. A media type can start the query:screen,printorall(the default if you leave it out);onlyis an old keyword that hid queries from very old browsers and is no longer needed. - 5
Media queries add rules; they don't create a stronger kind of rule. Inside the braces, rules compete in the normal cascade: same specificity means the later one wins. That's why mobile-first CSS puts the base rules first and the
min-widthqueries after them, in increasing order. Put a query above the base rule with the same selector and the base rule silently wins at every width. - 6
Units matter.
emandremin a media query are relative to the browser's default font size (normally 16px, but users can change it), not to anything your CSS sets onhtml. So(min-width: 40em)is 640px for most people and grows if a user picks bigger text, which keeps your layout readable for them. JavaScript can test the same conditions withwindow.matchMedia("(min-width: 40rem)"), which returns an object whose.matchesistrueorfalseand which fires achangeevent when the answer changes.
Explain it without notes
What is a media query, and what happens to the rules inside it?
Explain min-width vs max-width and why mobile-first code mostly uses min-width.
What problem does the range syntax solve?
How do you combine media queries with "and", "or" and "not"?
Why can a rule inside a matching media query still lose?
Practice
Make .sidebar hidden on screens narrower than 48rem and shown as a 16rem-wide column on wider screens, mobile first, with range syntax.
Write one media query that applies when the screen is in landscape and at most 500px tall (a phone on its side), and reduce the header's padding there.
In JavaScript, log "wide" or "narrow" now, and again every time the viewport crosses 48rem.
Trade-offs
- ↔
Range syntax is clearer and gap-free, but
min-width/max-widthare what most existing code and tutorials use; teams usually pick one style and stick to it. - ↔
Many small breakpoints give precise control but make CSS harder to follow and test; a few content-based breakpoints plus intrinsic layouts are easier to maintain.
Done when you can
I can write
@mediawithmin-width,max-widthand range syntax.I can use
orientation, media types andand, comma andnot.I order mobile-first queries after the base rules, small to large.
I can spot an invalid query (it silently never matches).
I can use
matchMediaand itschangeevent in JavaScript.