@media: The Complete Deep-Dive
@media is the workhorse of responsive design. It applies a block of CSS only when the environment matches: viewport size, device orientation, display resolution, input capabilities, or user preferences like dark mode and reduced motion. This page covers the full syntax — including the modern range syntax — and the strategy questions: mobile-first vs desktop-first, and which features to query.
Anatomy of a Media Query
@media media-type and (media-feature) {
/* rules applied only when the query matches */
}
/* Real example */
@media screen and (min-width: 768px) {
.nav { display: flex; }
}
/* The media type is optional — this is equivalent for screens */
@media (min-width: 768px) {
.nav { display: flex; }
}Media Types
Type | Matches | Common use |
|---|---|---|
all | Every device (default) | Rarely written explicitly |
screen | Monitors, phones, tablets | Almost always implied |
Print preview and printed pages | Print stylesheets | |
speech | Screen readers reading aloud | Rarely used in practice |
/* Hide navigation and expand content when printing */
@media print {
nav, footer, .ads { display: none; }
main { width: 100%; }
a::after { content: " (" attr(href) ")"; }
}Width and Height Features
The classic responsive features. min-width means "at this width and above"; max-width means "at this width and below".
/* Applies at 768px and wider */
@media (min-width: 768px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
/* Applies at 767.98px and narrower */
@media (max-width: 767.98px) {
.sidebar { display: none; }
}
/* Height queries — useful for short landscape phones */
@media (max-height: 500px) {
.hero { min-height: auto; padding-block: 2rem; }
}rem (e.g. 48rem instead of768px) respect users who increase their default font size — the layout adapts earlier, exactly when text needs more room.Modern Range Syntax
Media Queries Level 4 lets you write comparisons directly, which is clearer and avoids the off-by-0.02px dance between max-width and min-width:
/* Old way */
@media (min-width: 768px) and (max-width: 1199.98px) { ... }
/* New range syntax — supported in all modern browsers */
@media (768px <= width < 1200px) {
.layout { padding-inline: 2rem; }
}
/* Single comparisons work too */
@media (width >= 768px) { ... }
@media (width < 480px) { ... }Orientation
@media (orientation: landscape) {
.gallery { grid-template-columns: repeat(4, 1fr); }
}
@media (orientation: portrait) {
.gallery { grid-template-columns: repeat(2, 1fr); }
}portrait. Prefer width queries for layout decisions and orientation only when the distinction genuinely matters.User Preference Queries
These features query what the user asked their operating system for. Respecting them is table stakes for accessibility.
/* Dark mode */
@media (prefers-color-scheme: dark) {
:root {
--bg: #0f1115;
--text: #e6e6e6;
}
}
/* Reduced motion — never ignore this one */
@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;
}
}
/* Higher contrast */
@media (prefers-contrast: more) {
.card { border: 2px solid currentColor; }
}Input Capability: hover and pointer
Instead of guessing "is this a phone?", query what the primary input can actually do. hover asks whether the device can hover; pointer asks how precise the pointing device is.
Query | Matches | Typical device |
|---|---|---|
(hover: hover) | Primary input can hover | Mouse, trackpad |
(hover: none) | No hover capability | Touchscreen |
(pointer: fine) | Precise pointer | Mouse, stylus |
(pointer: coarse) | Imprecise pointer | Finger on touchscreen |
(any-hover: hover) | Any attached input hovers | Laptop with touchscreen |
/* Only add hover effects where hover exists —
avoids "sticky hover" on touchscreens */
@media (hover: hover) and (pointer: fine) {
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgb(0 0 0 / 0.15);
}
}
/* Bigger tap targets for coarse pointers */
@media (pointer: coarse) {
.icon-button { min-width: 44px; min-height: 44px; }
}Resolution
/* Serve crisper assets on high-density (retina) screens */
@media (min-resolution: 2dppx) {
.logo {
background-image: url("logo@2x.png");
background-size: 120px 40px;
}
}srcset on the img element usually beats resolution media queries — the browser picks the best candidate automatically.Combining Queries: and, or, not
/* AND — every condition must match */
@media (min-width: 768px) and (orientation: landscape) { ... }
/* OR — comma-separated list, any query may match */
@media (max-width: 480px), (orientation: portrait) { ... }
/* NOT — negates one whole query (needs a media type) */
@media not screen and (min-width: 768px) { ... }
/* Level 4: or / not inside conditions */
@media ((min-width: 768px) or (orientation: landscape)) { ... }Mobile-First vs Desktop-First
Mobile-first writes base styles for the smallest screen, then layers on min-width queries as space grows. Desktop-first does the reverse with max-width. Mobile-first is the recommended default: the base experience works everywhere, and each breakpoint adds complexity instead of undoing it.
/* Mobile-first: base = single column */
.cards {
display: grid;
gap: 1rem;
}
@media (min-width: 48rem) {
.cards { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 75rem) {
.cards { grid-template-columns: repeat(3, 1fr); }
}/* Desktop-first: you spend queries UNDOING styles */
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
@media (max-width: 74.99rem) {
.cards { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 47.99rem) {
.cards { grid-template-columns: 1fr; }
}Media Queries Beyond Stylesheets
<!-- Conditional stylesheet loading --> <link rel="stylesheet" href="print.css" media="print" /> <link rel="stylesheet" href="wide.css" media="(min-width: 1024px)" /> <!-- Responsive images --> <picture> <source srcset="hero-wide.avif" media="(min-width: 800px)" /> <img src="hero.avif" alt="Mountain landscape" /> </picture>
In JavaScript, window.matchMedia('(min-width: 768px)') evaluates the same queries and fires change events — handy for syncing JS behavior with CSS breakpoints.
Key Takeaways
The media type (screen/print) is optional; features in parentheses do the real work.
Prefer the modern range syntax — (width >= 768px) — it is clearer and supported everywhere modern.
Query capabilities (hover, pointer) and preferences (color scheme, motion), not devices.
Always honor prefers-reduced-motion.
Go mobile-first: base styles for small screens, min-width queries to enhance.
Set breakpoints where your content breaks, in rem, not at device widths.
Remember media queries also work on link tags, picture sources, and window.matchMedia.