Media Queries
Learn to write @media rules that adapt styles based on viewport size, orientation, and user preferences like dark mode and reduced motion, plus a first look at @supports feature queries.
Media queries are the mechanism that makes responsive design possible: a block of CSS that only applies when some condition about the browsing environment is true. The most familiar condition is screen width, but media queries can also react to orientation, and increasingly to accessibility and system preferences the user has set — like preferring dark mode or preferring less motion. This lesson covers the syntax and the conditions you'll use most often.
Basic @media syntax
A media query starts with @media, followed by one or more conditions, followed by a block of normal CSS rules in curly braces. Anything inside that block only takes effect when the condition is true. @media (min-width: 768px) { .nav { display: flex; } } means: 'apply display: flex to .nav, but only when the viewport is at least 768px wide.'
min-width matches when the viewport is that width or wider; max-width matches when it's that width or narrower. You can combine several conditions with and: @media (min-width: 600px) and (max-width: 999px) { ... } targets only viewports in that range — commonly used for a 'tablet-only' style. A comma between queries acts as OR: @media (max-width: 500px), (orientation: portrait) { ... } matches if either condition is true.
Orientation
orientation: portrait matches when the viewport is taller than it is wide; orientation: landscape matches when it's wider than tall. This is useful for layouts that should differ between a phone held upright versus sideways, or between a phone and a widescreen monitor regardless of exact pixel width. It's less commonly needed than width-based queries but valuable for things like full-screen video players or games.
prefers-color-scheme: respecting dark mode
Most operating systems let users choose a light or dark theme system-wide. The prefers-color-scheme media feature lets your CSS detect that choice and respond: @media (prefers-color-scheme: dark) { body { background: #111; color: #eee; } }. This runs automatically based on the OS setting — no JavaScript needed for the basic case.
A common, robust pattern is to define your default (light) colors as CSS custom properties on :root, then redefine those same custom properties inside a prefers-color-scheme: dark media query, so every component that references the variables updates automatically without duplicating each individual style rule.
prefers-reduced-motion: an accessibility must-know
Some users experience real discomfort, dizziness, or even seizures from large animated motion on screen — this is a documented accessibility concern, not just a preference. Operating systems expose a 'reduce motion' system setting for exactly this reason, and CSS can detect it with @media (prefers-reduced-motion: reduce) { ... }.
The typical pattern is to write your animations and transitions normally, and then add a reduced-motion override near the end of your stylesheet that either removes or drastically shortens them: @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }. You'll use this pattern again when this course covers animations directly, but it's introduced here because it is fundamentally a media query.
A quick look at @supports (feature queries)
@media queries test things about the environment (screen size, preferences). A related but different at-rule, @supports, tests whether the browser understands a given CSS feature at all — a 'feature query'. For example @supports (display: grid) { ... } only applies its rules in browsers that support CSS Grid, letting you provide a simpler fallback layout outside the block for older browsers.
You'll work with @supports in more depth later in the course; for now, just recognize that it looks similar to @media but answers a different question — 'can this browser do X?' rather than 'is condition X currently true?'.
Common media query patterns
Conditions inside a media query's parentheses are combined with and; use a comma between whole queries for OR logic. @supports checks browser capability, @media checks environment/user-preference conditions.
/* Width range */
@media (min-width: 768px) { /* ... */ }
@media (max-width: 767px) { /* ... */ }
@media (min-width: 600px) and (max-width: 999px) { /* ... */ }
/* Orientation */
@media (orientation: landscape) { /* ... */ }
/* User preferences */
@media (prefers-color-scheme: dark) { /* ... */ }
@media (prefers-reduced-motion: reduce) { /* ... */ }
/* Feature support (not a media query, but related) */
@supports (display: grid) { /* ... */ }Code Examples
Every color in the page is written in terms of the three custom properties. Redefining them once inside the dark-mode media query updates the whole page consistently, without touching body, a, or any other rule.
@media (prefers-color-scheme: dark) { :root { --bg: #121212; ... } }
Users who have not asked for reduced motion still get the smooth hover lift. Users who have set the OS-level reduced-motion preference get the same hover effect instantly, with no animated movement, because the second rule overrides the transition.
:root {
--bg: #ffffff;
--text: #1a1a1a;
--accent: #2b6cb0;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #121212;
--text: #f0f0f0;
--accent: #63b3ed;
}
}
body {
background: var(--bg);
color: var(--text);
}
a { color: var(--accent); }.card {
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-6px);
}
@media (prefers-reduced-motion: reduce) {
.card {
transition: none;
}
}Media query decision flow
Media queries are re-evaluated continuously — resizing a browser window live-updates which rules apply.
Browser loads CSS
│
▼
@media (min-width: 768px) → condition true? ── no ──▶ rules skipped
│ yes
▼
rules inside the block ARE applied
(re-evaluated live as the window is resized)Important Rules
min-width = 'this width or wider'; max-width = 'this width or narrower'.
and inside one query combines conditions (all must be true); a comma between queries is OR (any can be true).
Always provide a prefers-reduced-motion: reduce fallback for any non-trivial animation or transition.
prefers-color-scheme reflects the OS/browser setting automatically — it requires no JavaScript for basic light/dark theming.
@supports tests browser feature support; @media tests environment and user-preference conditions — they solve different problems.
Where this shows up in real products
Nearly every modern content site or app ships a dark mode driven by prefers-color-scheme, because users increasingly expect their OS-wide theme choice to be respected automatically rather than requiring a manual toggle. Similarly, accessibility audits (including automated tools and legal compliance checks like WCAG) specifically look for prefers-reduced-motion support on any site with meaningful animation — it's considered a baseline requirement, not an extra.
@media (min-width: 600px), (max-width: 999px) { .nav { display: flex; } }
@media (min-width: 600px) and (max-width: 999px) { .nav { display: flex; } }
A comma is OR, not AND. The 'wrong' version matches almost every possible screen width (anything ≥600px OR anything ≤999px covers nearly the whole range), while the intended 'tablet-only' range needs and.
Building an animation-heavy page with no reduced-motion consideration at all
Wrap non-essential motion in a @media (prefers-reduced-motion: reduce) override that removes or shortens it
Ignoring reduced-motion preferences isn't just a missed nicety — for some users it's an accessibility barrier that can cause genuine physical discomfort.
[Predict the Output] A user's operating system is set to dark mode, and their browser window is 900px wide. Given this CSS, what color is the page background? body { background: white; } @media (min-width: 768px) { body { background: yellow; } } @media (prefers-color-scheme: dark) { body { background: black; } } body { background: white; } @media (min-width: 768px) { body { background: yellow; } } @media (prefers-color-scheme: dark) { body { background: black; } } A. white, because it's the first rule declared B. yellow, because the min-width query matches and comes after the base rule C. black, because the prefers-color-scheme query matches and is the last matching rule declared, so it wins the cascade D. It's impossible to know without JavaScript
C. black, because the prefers-color-scheme query matches and is the last matching rule declared, so it wins the cascade — Both media queries match (900px ≥ 768px is true, and dark mode is on), so this comes down to normal CSS cascade order: since the dark-mode rule is declared after the min-width rule in the source and has equal specificity, it wins, making the background black.
Write a media query that applies a flex-direction: column layout to .toolbar only when the viewport is both under 500px wide AND in portrait orientation.
@media (max-width: 500px) and (orientation: portrait) { .toolbar { flex-direction: column; } }
You need two conditions joined with and inside a single @media rule: a max-width condition and an orientation condition.