Beyond Width: The Full Picture
The @media rule was introduced in CSS2 back in 1998, but for most of the following decade it was used almost exclusively to target print stylesheets. Responsive web design, popularised by Ethan Marcotte's 2010 article, brought viewport-width queries into the mainstream. For years, that was the whole story: you wrote breakpoints, you adjusted layouts, you called it responsive.
The modern CSS Media Queries specification — currently at Level 5 — is a different beast entirely. It introduces a category called media features that lets you query not just the size of the viewport but the capabilities and preferences of the device and the person using it. You can ask whether the user prefers dark mode, whether they have asked the OS to reduce animations, whether the primary input is a mouse or a finger, and whether the display is running as a standalone installed app.
These queries are not niche. Dark mode alone is enabled by a majority of users on mobile platforms. Touch-only input is the norm for the 116 smartphones and 14 tablets in the DeviceSpecsHub database — that is over 70% of all tracked devices. Writing CSS that ignores these realities is writing CSS for a world that no longer exists.
This guide walks through every major non-dimension media feature, explains what it detects, shows real code examples, and flags the browser support situation so you know what you can ship today versus what needs a progressive enhancement wrapper.
prefers-color-scheme: Respecting the User's Theme
prefers-color-scheme is the most widely adopted non-dimension media feature. It reads the operating system's light/dark mode setting and lets you apply a matching stylesheet. The two values are light and dark.
/* Base styles are light — override for dark */
body {
background: #ffffff;
color: #1e293b;
}
@media (prefers-color-scheme: dark) {
body {
background: #0f172a;
color: #e2e8f0;
}
a {
color: #67e8f9; /* cyan-300 — readable on dark backgrounds */
}
}The common mistake is to write all your dark-mode styles as overrides at the end of your stylesheet. This works, but it creates specificity battles and makes the code hard to maintain. A better pattern is to use CSS custom properties (variables) and only change the variable values inside the media query:
:root {
--bg: #ffffff;
--text: #1e293b;
--border: #e2e8f0;
--accent: #0891b2; /* cyan-600 */
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0f172a;
--text: #e2e8f0;
--border: #1e293b;
--accent: #67e8f9; /* cyan-300 — lighter for dark bg */
}
}
/* All components reference variables — no duplication */
.card {
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
}This approach means you define each component's styles once and the theme switch happens automatically. It also makes it trivial to add a manual theme toggle later — you just apply a data-theme="dark" attribute to :root and mirror the media query values there.
prefers-color-scheme media query and a .dark class that set conflicting values. Pick one source of truth.Testing prefers-color-scheme
In Chrome DevTools, open the Rendering panel (⋮ → More tools → Rendering) and find "Emulate CSS media feature prefers-color-scheme". This lets you toggle between light and dark without changing your OS setting, which is far faster for development. Firefox has an equivalent in its DevTools toolbar.
prefers-reduced-motion: Animations That Don't Hurt
Some users experience motion sickness, vertigo, or seizures triggered by animations and transitions. macOS, iOS, Android, and Windows all expose a system-level "reduce motion" setting. The prefers-reduced-motion media feature reads that setting.
The values are no-preference (default — animations are fine) and reduce (the user has asked for less motion). The correct approach is to write your full animations first, then override them inside the media query:
/* Full animation by default */
.hero-image {
animation: float 3s ease-in-out infinite;
}
.modal {
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Respect the user's preference */
@media (prefers-reduced-motion: reduce) {
.hero-image {
animation: none;
}
.modal {
transition: opacity 0.15s ease; /* Fade is fine — movement is not */
}
/* Nuclear option: disable ALL transitions and animations */
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}The "nuclear option" at the bottom is a useful safety net for large codebases where you cannot audit every animation individually. It is aggressive — it will also kill useful micro-interactions like button press feedback — so use it as a starting point and then selectively re-enable the transitions that are purely opacity-based and safe.
In JavaScript, you can read the same preference with window.matchMedia('(prefers-reduced-motion: reduce)').matches. This is useful for controlling animations driven by JavaScript libraries (GSAP, Framer Motion, etc.) that CSS cannot reach.
prefers-reduced-motion is not just a nice-to-have. The WCAG 2.1 Success Criterion 2.3.3 (AAA) explicitly requires that motion animation triggered by interaction can be disabled. Many large organisations now require WCAG AA compliance as a minimum, and respecting this preference is increasingly expected even at that level.hover and pointer: Knowing Your Input Device
This is one of the most practically useful and most underused media feature pair in CSS. The hover feature tells you whether the primary pointing device can hover over elements without activating them. The pointer feature tells you the precision of that pointing device.
| Feature | Value | Meaning | Typical Device |
|---|---|---|---|
| hover | hover | Can hover without activating | Desktop mouse, laptop trackpad |
| hover | none | Cannot hover (or no pointer) | Touchscreen phone, tablet, keyboard-only |
| pointer | fine | High-precision pointer | Mouse, stylus, trackpad |
| pointer | coarse | Low-precision pointer | Finger on touchscreen |
| pointer | none | No pointer device | Keyboard-only, TV remote |
The key insight is that these features describe the primary input device. A laptop with a touchscreen reports hover: hover and pointer: finebecause the trackpad is the primary input, even though the touchscreen is also present. The any-hover and any-pointer variants let you check if any input device has that capability.
/* Only show hover effects when hovering is actually possible */
@media (hover: hover) and (pointer: fine) {
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
}
.nav-link::after {
content: '';
display: block;
height: 2px;
background: currentColor;
transform: scaleX(0);
transition: transform 0.2s ease;
}
.nav-link:hover::after {
transform: scaleX(1);
}
}
/* Make touch targets larger on coarse-pointer devices */
@media (pointer: coarse) {
.button {
min-height: 44px; /* Apple HIG minimum tap target */
min-width: 44px;
padding: 12px 20px;
}
.checkbox-label {
padding: 12px 0; /* Expand the tap area */
}
}The 44px minimum tap target size comes from Apple's Human Interface Guidelines and is echoed by Google's Material Design (48px) and WCAG 2.5.5 (24px minimum, 44px recommended). Using pointer: coarse to apply these sizes only on touch devices means you are not wasting space on desktop layouts where a mouse can hit a 16px target without difficulty.
Why hover effects on touchscreens are a problem
On iOS and Android, a "hover" state is simulated on the first tap. This means if you have a hover effect that changes background colour or shows a tooltip, the user has to tap twice to activate the underlying link — once to trigger the hover state, and once to actually navigate. Wrapping all your hover effects in @media (hover: hover) eliminates this problem entirely.
prefers-contrast: High-Contrast Mode Support
Windows High Contrast Mode and similar accessibility settings on other platforms are used by people with low vision, colour blindness, or other visual impairments. The prefers-contrast media feature lets you detect and respond to these settings.
| Value | Meaning |
|---|---|
| no-preference | No contrast preference set |
| more | User prefers higher contrast |
| less | User prefers lower contrast (rare) |
| forced | OS is forcing a colour scheme (Windows High Contrast) |
/* Enhance borders and text contrast when requested */
@media (prefers-contrast: more) {
.card {
border: 2px solid #000000; /* Stronger border */
}
.muted-text {
color: #374151; /* Darker than the default slate-500 */
}
.badge {
outline: 2px solid currentColor; /* Ensure badges are visible */
}
}
/* Handle Windows Forced Colors mode */
@media (forced-colors: active) {
.custom-checkbox {
forced-color-adjust: none; /* Opt out of forced colours for this element */
border: 2px solid ButtonText;
background: ButtonFace;
}
}The forced-colors: active query is specifically for Windows High Contrast Mode, where the OS replaces all colours with a small system palette. Custom-styled form controls often break completely in this mode because their visual styling is entirely CSS-based. The forced-color-adjust: none property lets you opt a specific element out of the forced colour scheme so you can apply your own high-contrast styles.
prefers-reduced-data: Respecting Data Limits
This is the newest and least supported of the preference queries, but it is worth knowing about. prefers-reduced-data detects when a user has enabled a "data saver" or "low data" mode on their device or browser. The intended use is to serve lower-resolution images, skip autoplay video, and avoid loading non-essential resources.
/* Serve smaller images on data-saver connections */
@media (prefers-reduced-data: reduce) {
.hero-background {
background-image: url('/hero-small.webp'); /* 50KB instead of 500KB */
}
.decorative-video {
display: none; /* Skip the background video entirely */
}
}
/* In JavaScript: skip analytics and tracking pixels */As of early 2026, prefers-reduced-data is supported in Chromium-based browsers behind a flag but has not shipped in stable Firefox or Safari. Use it as progressive enhancement — it will not hurt browsers that do not support it, and it will benefit users on the browsers that do.
display-mode: Detecting Installed PWAs
Progressive Web Apps can be installed to a device's home screen and launched in a standalone window without browser chrome. The display-mode media feature lets you detect this and adjust your UI accordingly — for example, hiding a "Install App" banner when the app is already installed, or showing a custom title bar.
| Value | Meaning |
|---|---|
| browser | Running in a standard browser tab |
| standalone | Installed PWA, no browser UI |
| minimal-ui | Installed PWA with minimal browser controls |
| fullscreen | Running fullscreen (games, video apps) |
/* Hide the install prompt when already installed */
.install-banner {
display: block;
}
@media (display-mode: standalone) {
.install-banner {
display: none;
}
/* Add padding for the status bar on mobile */
.app-header {
padding-top: env(safe-area-inset-top);
}
}
/* In JavaScript: same check */
const isInstalled = window.matchMedia('(display-mode: standalone)').matches;The env(safe-area-inset-top) value is worth noting here. On iPhones with a Dynamic Island or notch, the safe area inset ensures your header does not overlap the system UI. This is not a media query feature itself, but it is commonly paired withdisplay-mode: standalone because the notch overlap only becomes a problem when the browser chrome is removed.
Container Queries: The Component-Level Revolution
Container queries are not strictly a media feature — they query the size of a parent element rather than the viewport — but they belong in any modern responsive CSS discussion because they solve a problem that viewport-based media queries fundamentally cannot.
The problem: a card component that appears in a narrow sidebar should look different from the same card in a wide main content area. With viewport media queries, you cannot express this cleanly because the viewport width is the same in both cases. With container queries, you can.
/* 1. Establish a containment context on the parent */
.card-grid {
container-type: inline-size;
container-name: card-container;
}
/* 2. Query the container's width, not the viewport's */
@container card-container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
gap: 1rem;
}
.card-image {
aspect-ratio: 1;
border-radius: 8px;
}
}
/* Shorthand — container name is optional for simple cases */
.sidebar {
container-type: inline-size;
}
@container (max-width: 300px) {
.nav-item span {
display: none; /* Icon-only nav in narrow sidebars */
}
}Container queries shipped in all major browsers in 2023 and are now safe to use without a polyfill. They are particularly powerful for design systems and component libraries where the same component needs to adapt to many different layout contexts without knowing anything about the page it is placed in.
Combining Queries: The Real Power
The real power of modern media queries comes from combining them. You can use and, or (written as a comma), and not to build precise targeting rules.
/* Touch device in dark mode: large tap targets with dark colours */
@media (pointer: coarse) and (prefers-color-scheme: dark) {
.button {
min-height: 48px;
background: #0e7490; /* cyan-800 — visible on dark bg */
}
}
/* Desktop with reduced motion: no hover animations */
@media (hover: hover) and (prefers-reduced-motion: reduce) {
.card {
/* Keep the hover state visible but remove the transform */
transition: background-color 0.15s ease;
}
.card:hover {
background: #f8fafc; /* Subtle colour change instead of movement */
}
}
/* Narrow viewport OR narrow container */
@media (max-width: 640px) {
.sidebar { display: none; }
}
@container (max-width: 400px) {
.sidebar { display: none; }
}
/* Range syntax (CSS Media Queries Level 4) */
@media (400px <= width <= 768px) {
/* Tablet-only styles — cleaner than min/max pair */
}The range syntax at the bottom is a CSS Media Queries Level 4 feature that is now supported in all major browsers. It is significantly more readable than the old @media (min-width: 400px) and (max-width: 768px) pattern and should be preferred in new code.
Browser Support in 2026
| Feature | Chrome | Firefox | Safari | Edge | Safe to Ship? |
|---|---|---|---|---|---|
| prefers-color-scheme | 76+ | 67+ | 12.1+ | 79+ | Yes — full support |
| prefers-reduced-motion | 74+ | 63+ | 10.1+ | 79+ | Yes — full support |
| hover / pointer | 38+ | 64+ | 9+ | 12+ | Yes — full support |
| prefers-contrast | 96+ | 101+ | 14.1+ | 96+ | Yes — full support |
| forced-colors | 89+ | 89+ | No | 79+ | Progressive enhancement |
| display-mode | 45+ | 47+ | 13+ | 79+ | Yes — full support |
| Container queries | 105+ | 110+ | 16+ | 105+ | Yes — full support |
| prefers-reduced-data | Flag only | No | No | Flag only | Progressive enhancement only |
| Range syntax (@media) | 104+ | 63+ | 16.4+ | 104+ | Yes — full support |
The practical takeaway: every feature in this guide except prefers-reduced-data and forced-colors is safe to ship without a fallback in 2026. Both of those should be treated as progressive enhancement — they will not break anything in unsupported browsers, they will just not apply.
Practical Checklist: What to Add to Every Project
Here is a minimal baseline of non-dimension media queries that every production website should include. Think of this as the responsive CSS equivalent of a security checklist — not optional extras, but standard practice.
| Query | What to Do | Priority |
|---|---|---|
| prefers-color-scheme: dark | Provide a dark theme via CSS variables | High |
| prefers-reduced-motion: reduce | Disable or replace all animations and transitions | High |
| pointer: coarse | Ensure all interactive elements are at least 44px tall | High |
| hover: hover | Wrap all :hover effects so they only apply on hover-capable devices | Medium |
| prefers-contrast: more | Strengthen borders and darken muted text | Medium |
| display-mode: standalone | Hide install prompts; add safe-area padding | Low (PWA only) |
| forced-colors: active | Fix custom form controls that break in Windows High Contrast | Low |
| Container queries | Replace viewport breakpoints in reusable components | Medium |
The first three — dark mode, reduced motion, and coarse pointer — should be in every project from day one. They cover the vast majority of users who have accessibility or preference settings enabled, and they take less than an hour to implement correctly if you use CSS custom properties from the start.
The hover and container query items are slightly more involved but pay dividends on complex UIs. The remaining items are situational — add them when they are relevant to your specific project rather than as boilerplate.
If you are building for a wide range of devices — and if you are reading a site called DeviceSpecsHub, you probably are — the device database is a useful sanity check. Filter by category to see what input types and viewport sizes are common in your target audience, then use that data to prioritise which queries matter most for your specific project.
Share this article
Related Articles
The Complete Guide to Responsive Design Breakpoints in 2026
We analysed 185 real devices to find where viewport clusters naturally fall — and how to write CSS that handles all of them cleanly.
CSS Pixels vs Physical Pixels: The Complete Developer Guide
Your screen has millions of hardware dots. Your CSS has none of them. Understanding the gap — and the Device Pixel Ratio that bridges it — is one of the most practically useful things you can learn as a front-end developer.