Command Palette

Search for a command to run...

Hectal
PHASE 9Intermediate ~27 min· topic 2 of 7

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, @import or @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, print or all.
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
portrait if the viewport is at least as tall as wide, landscape if 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.

style.csswhole filecss
@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.

style.csswhole filecss
.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.

style.csswhole filecss
@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.

style.csswhole filecss
/* 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.

How the browser evaluates itdiagram
Rendering diagram…

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.

script.jswhole filejavascript
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. 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. 2

    Predict matchMedia

    In the second example, add q("not all") and q("(min-width: 0px) and print"). Predict both answers before you run. (Both are false.)

  3. 3

    Fix the order

    In the last example, move .b { color: gray; } above its media rule. Predict the new console line for b, then run. It should become rgb(0, 128, 0).

Code & diagrams

A layout that changes at two breakpoints Baseline 2023+ New tab

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

Press Run to see the page.

Console

Nothing logged yet.
Testing media queries from JavaScript New tab

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? false
Reading media rules from the stylesheet New tab

Each `@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

Press Run to see the page.

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
Order matters: a query above the base rule loses New tab

`(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

Press Run to see the page.

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) { ... }.

terminal
$ Page at any width
── what you'll see ──
The rules inside never apply. No error appears in the console.

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.

terminal
$ Page at some zoom levels
── what you'll see ──
At certain widths neither menu shows, because the viewport is a fraction like 599.5px.

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 to min-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-width is deprecated; use width.

Remember this

  1. 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. 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: 600px is true. There is also height, min-height and max-height, and **orientation**: portrait when the viewport is at least as tall as it is wide, landscape when it's wider than tall. Note that orientation is about the viewport, not the device: a desktop window that is taller than wide is portrait.

  3. 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: with max-width: 599px and min-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. 4

    You can combine tests. and means both must be true: @media (min-width: 600px) and (orientation: landscape). A comma means "or": @media (max-width: 400px), print applies on small screens or when printing (Level 4 also allows the word or inside a condition). not negates a whole query: @media not print. A media type can start the query: screen, print or all (the default if you leave it out); only is an old keyword that hid queries from very old browsers and is no longer needed.

  5. 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-width queries 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. 6

    Units matter. em and rem in 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 on html. 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 with window.matchMedia("(min-width: 40rem)"), which returns an object whose .matches is true or false and which fires a change event when the answer changes.

Explain it without notes

01

What is a media query, and what happens to the rules inside it?

02

Explain min-width vs max-width and why mobile-first code mostly uses min-width.

03

What problem does the range syntax solve?

04

How do you combine media queries with "and", "or" and "not"?

05

Why can a rule inside a matching media query still lose?

Practice

01

Make .sidebar hidden on screens narrower than 48rem and shown as a 16rem-wide column on wider screens, mobile first, with range syntax.

02

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.

03

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-width are 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 @media with min-width, max-width and range syntax.

  • I can use orientation, media types and and, comma and not.

  • 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 matchMedia and its change event in JavaScript.