
CSS Media Queries Level 4 and 5: Range Syntax and User Preferences
For a long time, "media query" meant one thing: @media (min-width: 768px). Width was the only question most of us asked the browser. But the specs have kept moving, and Media Queries Level 4 and Level 5 turned media queries into a much richer way of asking about the user's environment: how they interact with the page, what their screen can display, and what they've told their operating system they prefer.
In this article, we'll cover the new range syntax, the logical operators, the interaction and display features, and the user preference queries, with notes on what's safe to ship today.
A Quick Refresher on the Anatomy
A media query is a condition attached to a block of CSS:
@media screen and (min-width: 40rem) {
/* styles */
}
It has an optional media type (screen, print, or all) and one or more media features in parentheses. Level 4 deprecated most of the old media types, like tv, handheld, and projection, so in practice you'll only use screen, print, and all, and you can usually leave the type out entirely.
The Range Syntax
The biggest everyday improvement in Level 4 is range syntax. Instead of the min- and max- prefixes, you write comparisons with real operators.
/* Old */
@media (min-width: 40rem) {
}
@media (max-width: 39.99rem) {
}
@media (min-width: 40rem) and (max-width: 64rem) {
}
/* New */
@media (width >= 40rem) {
}
@media (width < 40rem) {
}
@media (40rem <= width <= 64rem) {
}
The supported operators are <, <=, >, >=, and =.
Why It's More Than Syntactic Sugar
The old prefixes were always inclusive. min-width: 40rem matches at exactly 40rem, and so does max-width: 40rem. If you had one set of styles for "small" and one for "large", both matched at the boundary pixel. The traditional fix was a fractional value like max-width: 39.99rem, which could still cause gaps on screens with fractional pixel widths, like some zoomed or high-density displays.
With range syntax, you can write boundaries that don't overlap or leave a gap:
@media (width < 40rem) {
.nav {
flex-direction: column;
}
}
@media (width >= 40rem) {
.nav {
flex-direction: row;
}
}
Every possible width matches exactly one of those.
Ranges Between Two Values
The two-sided form reads just like math notation:
@media (40rem <= width < 64rem) {
.sidebar {
display: none;
}
}
Range syntax works for any "range" feature: width, height, aspect-ratio, resolution, and so on.
@media (aspect-ratio > 16 / 9) {
/* ultra-wide screens */
}
@media (resolution >= 2dppx) {
/* high-density displays */
}
Support
Range syntax is supported in all current versions of Chrome, Edge, Firefox, and Safari, and has been for a few years. Unless you need to support quite old browsers, you can use it freely. If you do, the old prefixed form is the fallback. Browsers that don't understand a query treat it as not matching, so a range query in an old browser simply never applies.
Logical Operators: not, and, or
Level 4 cleaned up how you combine conditions.
/* and: both must be true */
@media (width >= 40rem) and (orientation: landscape) {
}
/* or: either may be true (a comma works the same way) */
@media (width < 30rem) or (height < 30rem) {
}
/* not: negate a condition, now allowed on individual features */
@media not (hover: hover) {
}
/* grouping with parentheses */
@media (width >= 40rem) and ((hover: hover) or (pointer: fine)) {
}
Before Level 4, not could only negate an entire query, including its media type, which led to confusing results. Now you can apply it to a single feature in parentheses. The or keyword is a readable alternative to the comma list, and it can be nested inside grouped conditions where a comma can't.
Interaction Media Features
Screen width tells you nothing about how someone is using the page. A 1200px-wide tablet may have no mouse, and a narrow browser window on a laptop has a precise trackpad. Level 4 added features that describe input devices directly.
hover and pointer
hover: hovermeans the primary input can hover (a mouse or trackpad).hover: nonemeans it can't (most touchscreens).pointer: finemeans the primary input is precise (a mouse or stylus).pointer: coarsemeans it's imprecise (a finger).pointer: nonemeans there's no pointing device (for example, some keyboard-only or TV setups).
/* Only show hover effects where hover actually exists */
@media (hover: hover) {
.card:hover {
box-shadow: 0 8px 24px rgb(15 23 42 / 0.15);
}
}
/* Bigger tap targets for touch */
@media (pointer: coarse) {
.toolbar button {
min-width: 44px;
min-height: 44px;
}
}
any-hover and any-pointer
hover and pointer describe the primary input. any-hover and any-pointer check whether any available input matches. A touchscreen laptop might report pointer: coarse if the touchscreen is considered primary, but also any-pointer: fine because a trackpad is present.
@media (any-pointer: coarse) {
/* at least one input is a finger: keep controls touch-friendly */
.slider-thumb {
width: 28px;
height: 28px;
}
}
A good rule of thumb is to use hover and pointer for enhancements like hover effects, and any-pointer: coarse for safety features like larger targets.
Display Quality Features
resolution
Level 4 standardized resolution with the dppx unit, replacing the old prefixed -webkit-min-device-pixel-ratio:
.logo {
background-image: url("/images/logo.png");
}
@media (resolution >= 2dppx) {
.logo {
background-image: url("/images/logo@2x.png");
}
}
For CSS backgrounds, image-set() is often a cleaner choice, and for <img> elements, use srcset.
update
update describes how quickly the output device can change what it shows. It's fast for normal screens, slow for e-ink readers, and none for print.
@media (update: slow) {
* {
animation: none !important;
transition: none !important;
}
}
color-gamut and dynamic-range
These let you target wide-color and HDR displays:
.brand-accent {
color: rgb(236 72 153);
}
@media (color-gamut: p3) {
.brand-accent {
color: color(display-p3 0.95 0.25 0.6);
}
}
@media (dynamic-range: high) {
/* HDR-capable display */
}
Support for color-gamut is good across modern browsers. dynamic-range is newer, so treat it as a progressive enhancement.
User Preference Media Features
This is where Level 5 comes in, and it's the most important group for accessibility. These features reflect settings the user chose in their operating system or browser. Respecting them is how you honor choices people made for comfort, readability, or medical reasons.
prefers-color-scheme
The best known of the group. It's light or dark:
:root {
--bg: #ffffff;
--text: #0f172a;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0b1120;
--text: #e2e8f0;
}
}
body {
background: var(--bg);
color: var(--text);
}
Also declare color-scheme: light dark on :root so form controls and scrollbars match. Modern CSS also has the light-dark() function, which picks one of two values based on the used color scheme and can reduce the amount of media-query code you need:
:root {
color-scheme: light dark;
}
body {
background: light-dark(#ffffff, #0b1120);
color: light-dark(#0f172a, #e2e8f0);
}
prefers-reduced-motion
reduce means the user has asked for less motion, often because animation can trigger vestibular disorders, nausea, or migraines. It's widely supported and should be part of every project:
@media (prefers-reduced-motion: reduce) {
.carousel {
scroll-behavior: auto;
}
.hero__title {
animation: none;
}
}
The goal is less motion, not zero visual feedback. Replacing a slide with a quick fade is often better than removing the transition entirely.
prefers-contrast
more, less, custom, or no-preference. Use it to strengthen borders, remove low-contrast decorative text, or turn translucent backgrounds solid:
@media (prefers-contrast: more) {
.button {
border: 2px solid currentColor;
}
.muted {
color: inherit;
}
.glass-panel {
background: Canvas;
backdrop-filter: none;
}
}
forced-colors
forced-colors: active means the user is in a mode like Windows High Contrast (Contrast Themes), where the browser replaces your colors with a limited, user-chosen palette. Background images and box shadows may disappear. Use system color keywords so your UI survives:
@media (forced-colors: active) {
.toggle__track {
border: 1px solid ButtonText;
}
.toggle[aria-checked="true"] .toggle__thumb {
background: Highlight;
}
/* keep the brand logo's own colors; use sparingly */
.brand-logo {
forced-color-adjust: none;
}
}
The most common bug here is a custom checkbox or focus indicator drawn only with box-shadow or a background color, which vanishes in forced colors mode. A transparent outline or border becomes visible in forced colors, which makes it an easy safety net.
prefers-reduced-transparency
reduce means the user wants fewer translucent and blurred surfaces. Support is currently limited (Chromium-based browsers have it; check caniuse for others), so treat it as an enhancement:
@media (prefers-reduced-transparency: reduce) {
.site-header {
background: #ffffff;
backdrop-filter: none;
}
}
prefers-reduced-data
The spec defines prefers-reduced-data: reduce for users on metered or slow connections, which would be ideal for skipping web fonts or background video. As of this writing it isn't shipped by default in any major browser, so don't rely on it. If you use it, make sure the default experience is already reasonably lean.
Scripting and Display Mode
Two more features worth knowing:
/* JavaScript disabled or unavailable */
@media (scripting: none) {
.js-only {
display: none;
}
}
/* Installed as a PWA without browser UI */
@media (display-mode: standalone) {
.install-banner {
display: none;
}
}
scripting is supported in current major browsers. display-mode is widely supported for installed web apps.
Using Media Queries in JavaScript
Everything above works with window.matchMedia, which is handy for syncing JS behavior with user preferences:
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
function setupAnimations() {
if (reduceMotion.matches) {
document.documentElement.classList.add("reduce-motion");
} else {
document.documentElement.classList.remove("reduce-motion");
}
}
setupAnimations();
reduceMotion.addEventListener("change", setupAnimations);
Listening for change matters, because users can toggle these settings while your page is open.
Feature-Testing a Media Query
If you're not sure whether a browser understands a feature, remember that an unknown media feature makes the query evaluate as false. That's usually a safe default, but it can also bite you when you write a not query. not (unknown-feature) is also false, not true. Design so that the enhanced styles live inside the query and the base styles work without it.
Best Practices
- Prefer range syntax for new code. It's clearer and avoids boundary overlaps.
- Don't infer input from width. Use
hoverandpointerfor interaction decisions. - Treat preferences as requirements.
prefers-reduced-motion,prefers-contrast, andforced-colorsexist for accessibility, not decoration. - Test the modes. Chrome DevTools can emulate most preference features from the Rendering panel. Firefox and Safari have similar options, and your OS settings are the real test.
- Keep newer features progressive.
prefers-reduced-transparency,dynamic-range, andprefers-reduced-datashould enhance, never gate, the core experience.
Conclusion
Media queries have grown from a width check into a way of understanding the person on the other side of the screen. Range syntax makes breakpoints readable and precise. Interaction features let you design for fingers and mice instead of guessing from screen size. User preference features let you respect choices about color, motion, contrast, and transparency that people have already made.
Try auditing one of your stylesheets: convert the min-width queries to range syntax, check whether hover effects are gated behind (hover: hover), and turn on reduced motion and forced colors to see what breaks. You'll probably find a few quick wins.


