Media queries are the foundation of responsive design. They let CSS apply rules conditionally — based on screen width, orientation, colour scheme preference, input type, and dozens of other features. This cheat sheet covers every syntax you'll reach for, from basic breakpoints to modern container queries.
Quick reference
| Pattern | Code |
|---|---|
| Mobile-first min-width | @media (min-width: 768px) { } |
| Desktop-first max-width | @media (max-width: 767px) { } |
| Range (both bounds) | @media (min-width: 640px) and (max-width: 1023px) { } |
| Modern range syntax | @media (width >= 768px) { } |
| Portrait orientation | @media (orientation: portrait) { } |
| Dark mode | @media (prefers-color-scheme: dark) { } |
| Reduced motion | @media (prefers-reduced-motion: reduce) { } |
| Hover capable device | @media (hover: hover) { } |
| Print styles | @media print { } |
| High-DPI / Retina | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { } |
| Container query | @container sidebar (min-width: 300px) { } |
Basic syntax
A media query has an optional media type and one or more media features:
@media [media-type] [and (feature: value)] {
/* rules */
}
Media types:
| Type | Matches |
|---|---|
all |
Everything (default when omitted) |
screen |
Screens (monitors, phones, tablets) |
print |
Print preview and printed pages |
speech |
Screen readers |
You can omit the type — @media (min-width: 768px) defaults to all.
Logical operators:
| Operator | Example | Meaning |
|---|---|---|
and |
screen and (min-width: 768px) |
Both conditions must be true |
, (or) |
screen, print |
Either condition matches |
not |
not print |
Negates the whole query |
only |
only screen and (...) |
Hides from old parsers (legacy) |
Breakpoints
Mobile-first (recommended)
Write your base styles for small screens, then add min-width overrides as screens grow:
/* Base: mobile */
.container {
padding: 1rem;
}
/* Tablet and up */
@media (min-width: 768px) {
.container {
padding: 2rem;
}
}
/* Desktop and up */
@media (min-width: 1024px) {
.container {
max-width: 1280px;
margin: 0 auto;
}
}
Desktop-first
Write styles for large screens, override downward with max-width:
.sidebar {
width: 300px;
}
@media (max-width: 1023px) {
.sidebar {
width: 100%;
}
}
Common breakpoint systems
Tailwind CSS breakpoints (mobile-first):
| Name | Min-width |
|---|---|
sm |
640 px |
md |
768 px |
lg |
1024 px |
xl |
1280 px |
2xl |
1536 px |
Bootstrap 5 breakpoints:
| Name | Min-width |
|---|---|
sm |
576 px |
md |
768 px |
lg |
992 px |
xl |
1200 px |
xxl |
1400 px |
You don't have to use a framework's values — pick breakpoints based on where your content breaks, not device sizes.
Modern range syntax (Level 4)
The traditional min-/max- prefixes are wordy. CSS Media Queries Level 4 adds comparison operators:
/* Old syntax */
@media (min-width: 768px) and (max-width: 1023px) { }
/* New range syntax */
@media (768px <= width < 1024px) { }
/* or equivalently */
@media (width >= 768px) and (width < 1024px) { }
Supported in all modern browsers (Chrome 113+, Firefox 63+, Safari 16.4+).
Width and height
/* Minimum viewport width */
@media (min-width: 600px) { }
/* Maximum viewport width */
@media (max-width: 599px) { }
/* Exact range */
@media (min-width: 600px) and (max-width: 1199px) { }
/* Minimum viewport height */
@media (min-height: 600px) { }
/* Useful for landscape phones */
@media (max-height: 500px) and (orientation: landscape) {
.hero {
min-height: 100vh; /* avoid if keyboard pushes height down */
}
}
Orientation
@media (orientation: portrait) {
/* taller than wide */
.gallery {
grid-template-columns: 1fr;
}
}
@media (orientation: landscape) {
/* wider than tall */
.gallery {
grid-template-columns: repeat(3, 1fr);
}
}
Note: a square viewport matches neither portrait nor landscape.
User preference queries
These react to operating system or browser settings — use them to respect user choices.
Dark mode
:root {
--bg: #ffffff;
--text: #111111;
--card: #f8f8f8;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #111111;
--text: #eeeeee;
--card: #1e1e1e;
}
}
body {
background: var(--bg);
color: var(--text);
}
Reduced motion
Always add this guard around any animation:
.spinner {
animation: spin 1s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.spinner {
animation: none;
/* show a static fallback instead */
}
}
/* Or use the positive value to add motion */
@media (prefers-reduced-motion: no-preference) {
.hero-image {
transition: transform 0.4s ease;
}
}
Other preference queries
/* User prefers high contrast */
@media (prefers-contrast: more) {
.button {
border: 2px solid currentColor;
}
}
/* User prefers less data (e.g. metered connection) */
@media (prefers-reduced-data: reduce) {
.hero-video {
display: none;
}
}
/* Forced colours mode (Windows High Contrast) */
@media (forced-colors: active) {
.icon {
fill: ButtonText;
}
}
Input and pointer queries
/* Device has a fine pointer (mouse) */
@media (pointer: fine) {
.button {
padding: 0.5rem 1rem;
}
}
/* Device has a coarse pointer (touchscreen) */
@media (pointer: coarse) {
.button {
min-height: 44px; /* WCAG touch target */
padding: 0.75rem 1.5rem;
}
}
/* Device can hover (has a mouse) */
@media (hover: hover) {
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
}
/* Device cannot hover reliably (touch) */
@media (hover: none) {
.tooltip {
display: none; /* hide hover-only tooltips */
}
}
/* Common combination: mouse user */
@media (hover: hover) and (pointer: fine) {
.nav-item:hover .dropdown {
display: block;
}
}
Resolution / DPI
Use these to serve higher-quality images on Retina and HiDPI displays:
/* 1× screens */
.logo {
background-image: url('/logo.png');
}
/* 2× Retina and above */
@media (-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
.logo {
background-image: url('/logo@2x.png');
background-size: 120px 40px; /* half the pixel size */
}
}
/* 3× (iPhone Pro / Android flagships) */
@media (-webkit-min-device-pixel-ratio: 3),
(min-resolution: 288dpi) {
.logo {
background-image: url('/logo@3x.png');
}
}
Prefer
<img srcset>or CSSimage-set()over media queries for images — they're cleaner and let the browser decide.
Print styles
@media print {
/* Hide UI chrome */
nav,
footer,
.sidebar,
.cookie-banner,
button {
display: none !important;
}
/* Ensure black text on white */
body {
color: #000;
background: #fff;
font-size: 12pt;
}
/* Expand links to show URL */
a[href]::after {
content: ' (' attr(href) ')';
font-size: 0.8em;
color: #666;
}
/* Avoid page breaks inside cards */
.card,
table,
figure {
break-inside: avoid;
}
/* Add page break before major sections */
h1, h2 {
break-after: avoid;
}
}
Container queries
Container queries let a component respond to its parent's size instead of the viewport — essential for reusable components that appear in different contexts.
/* 1. Establish a containment context */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
/* 2. Query the container */
@container sidebar (min-width: 300px) {
.card {
display: flex;
flex-direction: row;
}
}
/* Without a name — queries the nearest containment ancestor */
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
grid-template-columns: 200px 1fr;
}
}
Container query units:
| Unit | Meaning |
|---|---|
cqw |
1% of container's inline size (width) |
cqh |
1% of container's block size (height) |
cqi |
1% of container's inline size |
cqb |
1% of container's block size |
cqmin |
Smaller of cqi/cqb |
cqmax |
Larger of cqi/cqb |
.card h2 {
font-size: clamp(1rem, 5cqw, 2rem);
}
Browser support: Chrome 105+, Firefox 110+, Safari 16+.
Combining queries
/* AND: both conditions must match */
@media screen and (min-width: 768px) and (orientation: landscape) {
.panel {
flex-direction: row;
}
}
/* OR (comma): either matches */
@media (max-width: 640px), (orientation: portrait) {
.nav {
flex-direction: column;
}
}
/* NOT: negates the whole query */
@media not print {
.no-print-label {
display: none;
}
}
/* NOT with and (Level 4 — use parentheses) */
@media not (prefers-color-scheme: dark) {
/* light mode only */
}
4 practical patterns
1. Responsive navigation
/* Mobile: hamburger */
.nav-links {
display: none;
}
.hamburger {
display: block;
}
/* Desktop: horizontal links */
@media (min-width: 1024px) {
.nav-links {
display: flex;
gap: 2rem;
}
.hamburger {
display: none;
}
}
2. Fluid typography with clamp (no media query needed)
/* Scales from 1rem at 320px to 1.5rem at 1280px */
h1 {
font-size: clamp(1.5rem, 2.5vw + 0.5rem, 3rem);
}
Use clamp() for font sizes and spacing — it often eliminates multiple breakpoints.
3. Dark mode with class override
@media (prefers-color-scheme: dark) {
:root:not(.light) {
--bg: #111;
--text: #eee;
}
}
.dark {
--bg: #111;
--text: #eee;
}
Add .dark or .light to <html> via JS to let users override the system preference.
4. Safe area insets (notch / home bar)
/* Needed on iPhone X and later */
body {
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
/* Only apply on devices that need it */
@supports (padding: env(safe-area-inset-bottom)) {
.fixed-footer {
padding-bottom: calc(1rem + env(safe-area-inset-bottom));
}
}
6 common mistakes
1. Writing desktop-first then struggling to override
/* Bad: specific desktop styles hard to undo on mobile */
.grid { display: grid; grid-template-columns: repeat(4, 1fr); }
@media (max-width: 768px) { .grid { display: block; } }
/* Good: start simple, enhance upward */
.grid { display: block; }
@media (min-width: 768px) { .grid { display: grid; grid-template-columns: repeat(4, 1fr); } }
2. Using max-width and min-width on the same breakpoint (overlap)
/* Bug: 768px matches both */
@media (min-width: 768px) { .box { color: blue; } }
@media (max-width: 768px) { .box { color: red; } }
/* Fix: use < instead of <= */
@media (min-width: 768px) { .box { color: blue; } }
@media (max-width: 767px) { .box { color: red; } }
/* or with Level 4 range: */
@media (width < 768px) { .box { color: red; } }
3. Forgetting the viewport meta tag
Without this in <head>, mobile browsers scale the page down and media queries fire at wrong widths:
<meta name="viewport" content="width=device-width, initial-scale=1">
4. Querying viewport width for components
If a component appears in a sidebar (300 px wide) and in the main content (800 px wide), viewport-based queries can't target both correctly. Use container queries instead.
5. Animating without prefers-reduced-motion
/* Always guard animations */
@media (prefers-reduced-motion: no-preference) {
.fade-in {
animation: fadeIn 0.5s ease;
}
}
6. Using min-device-width (deprecated)
min-device-width queried the device screen size, not the browser viewport. It's deprecated and unreliable — always use min-width (viewport).
6 FAQ
Q: Should I use pixels or ems for breakpoints?
Em-based breakpoints (min-width: 48em) scale with the user's browser font size setting. If a user has set 20 px base text, your breakpoint triggers at 960 px instead of 768 px, which can improve readability. Pixel breakpoints are simpler but ignore font preferences. Both approaches are valid — pick one and stay consistent.
Q: How many breakpoints do I need?
Start with two or three (mobile/tablet/desktop). Add a breakpoint only when your content actually breaks — not to match common device widths. A fluid layout with clamp() and CSS Grid auto-fill often needs zero breakpoints.
Q: Are container queries a replacement for media queries?
Complementary, not a replacement. Use media queries for global layout (nav, page structure, print, dark mode). Use container queries for reusable components that must respond to their context (cards, sidebars, widgets).
Q: How do I test media queries quickly?
Chrome DevTools → Toggle Device Toolbar (Ctrl+Shift+M / Cmd+Shift+M). Drag the viewport width to see breakpoints fire in real time. You can also add custom device presets.
Q: What is the only keyword for?@media only screen and (...) was introduced to hide styles from old browsers (IE 5–6) that didn't understand media features but would still apply the block. Modern browsers all support media features, so only is effectively obsolete — you can omit it.
Q: Can I use JavaScript to read media queries?
Yes. window.matchMedia('(min-width: 768px)').matches returns a boolean. Add a listener with .addEventListener('change', handler) to react when the query state changes:
const mq = window.matchMedia('(prefers-color-scheme: dark)');
function handleChange(e) {
document.documentElement.classList.toggle('dark', e.matches);
}
mq.addEventListener('change', handleChange);
handleChange(mq); // run once on load