Modern Media Queries: The Range Syntax, User Preferences, and What Container Queries Cannot Replace
Media queries now earn their place for user preferences, device characteristics, and page-level layout; container queries own component responsiveness.
Container queries did not make media queries obsolete. Believing that costs you real functionality. Container queries handle component-level size changes. They cannot detect user preferences like reduced motion or dark mode. They cannot set the page-level layout that responds to the screen. Divide the work cleanly: media queries own the page and the person, container queries own the component. The modern range syntax, (width >= 768px) instead of (min-width: 768px), shipped across all major engines and is Baseline widely available. Use it today. Pair it with container queries where each earns its place.
Which Media Queries Still Matter
Which media queries still matter now that container queries handle most size-based responsive work? The ones container queries cannot touch. User preference detection. Device characteristics like pointer and hover. Page-level layout. Container queries respond to the size of a container element. Media queries respond to the screen or device. That distinction is not cosmetic. A card that reflows when its container narrows needs a container query. The page grid that switches from one column to two when the screen widens needs a media query. Range syntax replaces the old min-width and max-width chains with a single, readable condition: (768px <= width <= 1023px). That is a direct syntax upgrade. The failure case: using a media query for component internals and a container query for the page shell. You get a page that does not reflow at the screen level and a card that ignores its own container. Know which tool owns which job.
Media Query Range Syntax in Production
Write the Parentheses Every Time
The production pattern for range syntax has one rule that saves debugging time: wrap the whole media feature in parentheses, including the comparison. @media (width >= 768px) is valid. @media width >= 768px is not. The W3C Media Queries Level 4 grammar requires parentheses around each media feature. Omitting them is the most common mistake in the wild. Here is the runnable pattern that replaces the old min-width chain:
/* Old pattern: min-width and max-width chains */
@media (min-width: 768px) and (max-width: 1023px) {
.page-grid {
grid-template-columns: 1fr 1fr;
}
}
/* New pattern: range syntax, single condition */
@media (768px <= width <= 1023px) {
.page-grid {
grid-template-columns: 1fr 1fr;
}
}
/* Reserve for browsers that do not support range syntax */
@supports not (width >= 0px) {
@media (min-width: 768px) and (max-width: 1023px) {
.page-grid {
grid-template-columns: 1fr 1fr;
}
}
}
When to Guard the Range Syntax
Range syntax is Baseline widely available. The @supports not (width >= 0px) guard provides a safe backup for the small percentage of users on older device-locked software, particularly iOS Safari on unsupported devices or Android WebView in apps that do not update. Use the guard only when you cannot afford a wrong layout. For most projects, range syntax is safe to write directly.
Prefers-Color-Scheme Media Query
prefers-color-scheme detects whether the user requested a light or dark theme at the operating system level. It is a user preference media query. It has nothing to do with screen width or container size, and container queries cannot replace it. Use this query to set the page-level colour scheme, not to adjust a component’s internal palette based on its own dimensions. The value is light or dark. The default is light when the user has no explicit preference. Here is the runnable pattern:
:root {
--surface: #ffffff;
--text: #1a1a1a;
}
@media (prefers-color-scheme: dark) {
:root {
--surface: #1a1a1a;
--text: #f0f0f0;
}
}
body {
background: var(--surface);
color: var(--text);
}
Pair this with a color-scheme property on the root element. Without it, the browser renders scrollbars, form controls, and other chrome in the wrong mode. You get a light scrollbar on a dark page. That looks broken. Do not try to use a container query to switch themes based on a container’s size. That is a user preference, not a dimension problem. The media query is the only correct tool. Range syntax does not apply here because prefers-color-scheme takes discrete values.
Prefers-Reduced-Motion Media Query
prefers-reduced-motion is the other user preference that container queries cannot replace. It matters for accessibility. The user has explicitly asked for less motion, either at the operating system level or via a browser setting. Honour that request. The values are no-preference and reduce. Write the reduced-motion styles as the default or as an explicit override. Do not rely on the absence of motion. Here is the complete, runnable sample:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
@media (prefers-reduced-motion: no-preference) {
.hero-animation {
animation: slide-in 0.6s ease-out;
}
}
The !important here is intentional and justified. You are overriding any animation or transition that another author’s code may have set, including third-party libraries. The 0.01ms duration is not zero, because zero can still trigger animation events in some engines, but it is imperceptible to the user. The failure case: putting the reduced-motion override in a container query. Container queries respond to size, not to user preference, so the animation plays regardless. Combine this query with prefers-reduced-transparency, which reduces translucent effects, and prefers-contrast for high-contrast needs. Each is a user preference. None is a size question.
Media Queries Container Queries Cannot Replace
User Preferences
There is a class of media queries that container queries cannot replace. Know exactly which ones they are. First, user preferences: prefers-color-scheme, prefers-reduced-motion, prefers-reduced-transparency, prefers-contrast, and prefers-reduced-data. These detect the user’s environment and settings, not the size of any element. Container queries have no mechanism to observe them.
Device Characteristics
Second, device characteristics. pointer and hover tell you whether the primary input is fine (a mouse) or coarse (a finger), and whether it can hover. That determines whether you enlarge tap targets or show hover states at all.
Page-Level Layout
Third, page-level layout. The screen width, height, and orientation are page concerns. Container queries are scoped to a container element. They cannot set the overall grid or the header layout. Here is the combination where the media query sets the page shell and the container query handles the component internals:
/* Media query: page-level layout */
@media (width >= 768px) {
.page-shell {
display: grid;
grid-template-columns: 240px 1fr;
gap: 1rem;
}
}
@media (width < 768px) {
.page-shell {
display: block;
}
}
/* Container query: component-level responsiveness */
.product-card {
container-type: inline-size;
}
@container (inline-size >= 400px) {
.product-card__details {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
@container (inline-size < 400px) {
.product-card__details {
display: block;
}
}
This is the correct division of labour. The media query decides how the page shell uses the available screen. The container query decides how the card uses its own space. Do not try to use a container query for the page shell. You would need to wrap the entire page in a container, and then you lose the screen-level knowledge that media queries give you for free, like orientation and width ranges.
Media Query Level 4 Syntax
Range Syntax and Boolean Operators
Media Queries Level 4 introduced range syntax and the new user preference features. It also deprecated device-width, device-height, and device-aspect-ratio. Those were based on the device’s nominal resolution rather than the actual screen, and they were unreliable for responsive design. The current syntax is a media condition that can combine features with and, or, and not. The headline change: instead of writing two conditions for a width band, write one. Here is the specification grammar in practice:
/* Level 3: min/max prefixes */
@media (min-width: 600px) and (max-width: 900px) { }
/* Level 4: range syntax */
@media (600px <= width <= 900px) { }
/* Level 4: single comparison */
@media (width >= 600px) { }
/* Level 4: or operator */
@media (width <= 600px) or (width >= 900px) { }
The or operator replaces the old comma-separated list of queries in most cases. You can still use commas, but or is more readable. The parenthesis requirement is absolute: @media (width >= 600px), not @media width >= 600px. The not operator negates the entire condition. only is a legacy keyword rarely needed today, but it still exists for old parsing behaviour.
Interaction Media Features
The spec also added pointer and hover as media features. These let you tailor interactions. Use @media (pointer: coarse) for touch screens. Use @media (hover: hover) for devices that can hover. The practical takeaway: write new media queries using range syntax and the new features. Reserve min-/max- prefixes for backups inside @supports guards.
User Preference Detection and Browser Support
User preference media queries are the reason media queries still earn their place. prefers-color-scheme, prefers-reduced-motion, prefers-contrast, and the newer prefers-reduced-data are all part of Media Queries Level 4. All are widely available in Baseline. The catch: prefers-reduced-data is the least supported of the group. It shipped in Chrome and Edge but not in Safari or Firefox as of the last build. You need a feature check before you rely on it. Write the query with a backup. If the browser does not support it, the user gets the default behaviour (full data). Here is the pattern:
@media (prefers-reduced-data: reduce) {
.hero-image {
content: url("placeholder.svg");
}
}
/* Default: full image when prefers-reduced-data is unsupported */
.hero-image {
content: url("hero-full.jpg");
}
The failure case: assuming prefers-reduced-data works everywhere and hiding the full image for everyone. A browser that does not parse the query drops the rule. The full image shows anyway. That is the safe default. The real gap is that Safari and Firefox do not implement prefers-reduced-data. You cannot rely on it for a global audience. The same logic applies to prefers-contrast. It is widely available. The values are no-preference, more, less, and custom. custom is a niche case. Write the more case explicitly. Let the default be the normal contrast. The browser is the final renderer. Your job is to describe what should happen under which conditions.
What to Write in Production Today
Layer the Three Tools
You have range syntax and container query syntax. The question is what to write in a production stylesheet. Use a layered approach. Start with the page-level media query using range syntax for the screen breakpoints. Add a container query on each component that reflows based on its own space. Then add the user preference media queries that override motion, colour, and data usage.
The Complete Pattern
Here is the runnable combination that uses all three:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
/* Page-level layout: media query with range syntax */
.page-shell {
display: block;
padding: 1rem;
}
@media (width >= 768px) {
.page-shell {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 2rem;
}
}
/* Component-level responsiveness: container query */
.card-list {
container-type: inline-size;
}
.card {
background: var(--surface);
padding: 1rem;
border-radius: 8px;
}
@container (inline-size >= 500px) {
.card__content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
}
/* User preference: reduced motion */
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
/* User preference: dark mode */
:root {
--surface: #fff;
--text: #111;
}
@media (prefers-color-scheme: dark) {
:root {
--surface: #222;
--text: #eee;
color-scheme: dark;
}
}
</style>
</head>
<body>
<main class="page-shell">
<aside class="card-list">
<article class="card">
<h2>Card title</h2>
<div class="card__content">
<p>Content here</p>
<p>More content</p>
</div>
</article>
</aside>
<section>
<p>Main content area</p>
</section>
</main>
</body>
</html>
Copy this pattern into your next project. The media query sets the page grid. The container query sets the card internal layout. The preference queries handle motion and theme. Avoid container-type: size when you only need inline-size. size requires the container to have a defined height, and it is more restrictive. Inline-size is the default for most component work.
Common Mistakes and How to Fix Them
Syntax and Scoping Errors
The most frequent failures with modern media queries are all syntax or scoping errors. The syntax error: missing parentheses. @media (width >= 768px) is valid. @media width >= 768px is not. The scoping error: using a media query where a container query belongs, or vice versa.
Fallback Override and Other Pitfalls
The third error is the backup override. Writing min-width inside a range-syntax rule without a @supports guard creates a duplicate rule that overrides the range version in engines that support both. Here is the failure case and the fix:
/* WRONG: fallback overrides range syntax in modern browsers */
@media (width >= 768px) {
.grid { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 768px) {
.grid { grid-template-columns: 1fr 1fr; }
}
/* RIGHT: guard the fallback */
@supports not (width >= 0px) {
@media (min-width: 768px) {
.grid { grid-template-columns: 1fr 1fr; }
}
}
The fourth mistake: using prefers-reduced-motion: reduce with !important on everything. That is correct as an override, but also provide a non-important override for your own components. This lets you selectively re-enable motion for a specific interaction that is essential. The fifth mistake: forgetting that prefers-color-scheme does not automatically set the browser chrome. You need the color-scheme property. The sixth: using device-width in a new stylesheet. It is deprecated and behaves differently across engines. Each of these is a small fix that prevents a page from looking broken.
Media Queries vs Container Queries vs Style Queries
| Feature | Media Queries | Container Queries | Style Queries |
|---|---|---|---|
| Responds to | Screen, device, user preference | Container element size | Computed value of a custom property on the container |
| Example condition | (width >= 768px) |
(inline-size >= 400px) |
(--theme: dark) |
| Can detect user preferences | Yes (prefers-color-scheme, prefers-reduced-motion) |
No | No |
| Typical use | Page layout, device characteristics, theme | Component internal reflow | Component variant based on a CSS variable |
| Baseline status | Widely available (core and range syntax) | Widely available (size queries) | Newly available (style queries) |
| Backup strategy | @supports not (width >= 0px) |
Graceful degradation to single-column | Custom property default |
This table distils the division of labour. Media queries are the broadest: they see the screen, the device, and the user’s preferences. Container queries see only the container element’s size. Style queries see only the custom property value. Do not treat them as interchangeable. They are three different tools for three different jobs.
What You Need to Know Now
Is range syntax safe to use in production? Yes. It is Baseline widely available. All major engines support it. The @supports not (width >= 0px) guard covers the small gap of older device-locked software.
Can container queries replace media queries for layout? No. Container queries cannot detect screen size, user preferences, or device characteristics. Use media queries for the page shell and header. Use container queries for component internals.
What is the backup for range syntax? Duplicate the rule with min-width/max-width prefixes inside an @supports not (width >= 0px) guard. Modern engines ignore the backup. Old engines use it.
Does prefers-reduced-data work everywhere? No. It is supported in Chrome and Edge but not in Safari or Firefox as of the last build. Write the default behaviour as the backup. Treat the query as progressive enhancement.
What to Do Next
Open your existing stylesheet. Find one media query that uses min-width or max-width in a chain. Rewrite it with range syntax. Change @media (min-width: 768px) and (max-width: 1023px) to @media (768px <= width <= 1023px). Test it. Then find one component that reflows based on its own width. Give it container-type: inline-size with a @container query. That one change moves the component from screen-dependent to container-dependent. That is the correct behaviour. The user preference queries are already written correctly in most codebases. Range syntax is the upgrade that makes them readable. Do not rewrite the whole stylesheet at once. Do one rule. Verify it. Move on. The syntax is the easy part. The division of labour is the skill.