CSS Media Queries: The Complete Guide for 2026
CSS media queries are the foundation of responsive web design. They let you apply different styles based on the characteristics of the user's device or browser: viewport width, screen resolution, color scheme preference, motion sensitivity, orientation, and more. Every responsive website you have ever used relies on media queries to adapt its layout from desktop monitors to mobile phones.
This guide covers everything you need to know about media queries in 2026: the basic syntax and media types, common breakpoints for mobile-first design, width and height queries, user preference features like prefers-color-scheme and prefers-reduced-motion, logical operators for combining conditions, the modern range syntax, container queries for component-level responsiveness, real-world responsive design patterns you can copy directly, and the most common mistakes to avoid. Whether you are building your first responsive layout or looking for an authoritative reference on every media feature, this is the guide to bookmark.
What Are Media Queries and Why Do They Matter?
A media query is a CSS technique that applies styles conditionally based on the result of a media test. The most common use is checking the viewport width to change layouts between mobile and desktop, but media queries go far beyond width. They can detect whether the user prefers dark mode, whether they have reduced motion enabled, the resolution of their display, whether the device is in portrait or landscape orientation, and whether the output is a screen or a printer.
Before media queries, developers built separate mobile sites (m.example.com) or used JavaScript to detect screen sizes and swap stylesheets. Media queries eliminated that entirely. With a single HTML document and CSS file, you can serve an optimized layout for every screen size and user preference. This approach, known as responsive web design, has been the industry standard since Ethan Marcotte coined the term in 2010.
In 2026, media queries are more powerful than ever. The specification has expanded to include user preference detection, a cleaner range syntax, and integration with container queries for component-level responsiveness. Understanding all of these capabilities is essential for building modern, accessible, performant websites.
Basic Syntax: The @media Rule
The @media rule is the entry point for all media queries. It wraps a block of CSS that only applies when the specified conditions are true.
/* Basic structure */
@media media-type and (media-feature) {
/* CSS rules that apply when the condition is met */
}
/* Example: apply styles only on screens wider than 768px */
@media screen and (min-width: 768px) {
.container {
max-width: 720px;
margin: 0 auto;
}
}
/* Example: apply styles for print */
@media print {
.no-print {
display: none;
}
body {
font-size: 12pt;
color: #000;
background: #fff;
}
}
Media Types
Media types specify the broad category of device the styles target. There are three media types you will actually use:
all— matches every device. This is the default when you omit the media type.screen— matches devices with screens (computers, tablets, phones).print— matches printers and print preview mode.
/* These two are identical: */
@media (min-width: 768px) { ... }
@media all and (min-width: 768px) { ... }
/* Target only screens (not print): */
@media screen and (min-width: 768px) { ... }
/* Target only print output: */
@media print { ... }
In practice, most developers omit the media type entirely and just write the feature query. The screen type is only needed when you want to explicitly exclude print output, and print is used for print-specific stylesheets. There are also deprecated types like tv, handheld, and projection that are no longer relevant and should not be used.
Media Features
Media features are the conditions you test. They are always wrapped in parentheses. Here are the most commonly used ones:
/* Viewport dimensions */
@media (min-width: 768px) { ... }
@media (max-width: 1024px) { ... }
@media (min-height: 600px) { ... }
@media (aspect-ratio: 16/9) { ... }
@media (orientation: portrait) { ... }
@media (orientation: landscape) { ... }
/* Display quality */
@media (min-resolution: 2dppx) { ... } /* Retina/HiDPI screens */
@media (-webkit-min-device-pixel-ratio: 2) { ... } /* Safari fallback */
/* User preferences */
@media (prefers-color-scheme: dark) { ... }
@media (prefers-reduced-motion: reduce) { ... }
@media (prefers-contrast: more) { ... }
/* Interaction capabilities */
@media (hover: hover) { ... } /* Device has hover capability */
@media (pointer: fine) { ... } /* Device has precise pointer (mouse) */
@media (pointer: coarse) { ... } /* Device has imprecise pointer (touch) */
Common Breakpoints: The Mobile-First Approach
Breakpoints are the viewport widths at which your layout changes. The most effective approach is mobile-first: write your base CSS for the smallest screens, then use min-width media queries to progressively enhance the layout for larger screens.
Why Mobile-First?
Mobile-first design has three significant advantages:
- Performance — mobile devices download less CSS because they only get the base styles. Desktop devices download additional rules, but they have the bandwidth to handle it.
- Simplicity — the mobile layout is usually the simplest (single column, stacked elements). Starting simple and adding complexity is easier than starting complex and removing it.
- Content priority — mobile-first forces you to decide what content matters most, which improves the design at every breakpoint.
Standard Breakpoint Values
/* Mobile-first breakpoints (min-width) */
/* Base styles: 0px to 479px (phones) */
/* No media query needed — these are your default styles */
/* Small: 480px+ (large phones, small tablets) */
@media (min-width: 480px) {
/* Slightly wider cards, 2-column small grids */
}
/* Medium: 768px+ (tablets) */
@media (min-width: 768px) {
/* 2-column layouts, side-by-side content */
}
/* Large: 1024px+ (small laptops, landscape tablets) */
@media (min-width: 1024px) {
/* 3-column layouts, sidebars appear */
}
/* Extra large: 1280px+ (desktops) */
@media (min-width: 1280px) {
/* Full desktop layout, max-width containers */
}
/* 2XL: 1536px+ (large desktops, ultrawide) */
@media (min-width: 1536px) {
/* Extra wide layouts, 4+ column grids */
}
These breakpoints align with popular CSS frameworks like Tailwind CSS and Bootstrap. However, the best practice is to set breakpoints where your content needs them, not at arbitrary device widths. Resize your browser and add a breakpoint wherever the layout breaks or looks awkward. This content-driven approach produces more resilient designs than targeting specific devices.
Comparison: Mobile-First vs Desktop-First
/* MOBILE-FIRST (recommended) */
/* Base: single column */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
/* Tablet: 2 columns */
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop: 3 columns */
@media (min-width: 1024px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* ---------------------------------------- */
/* DESKTOP-FIRST (not recommended) */
/* Base: 3 columns */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
/* Tablet: 2 columns */
@media (max-width: 1023px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Mobile: 1 column */
@media (max-width: 767px) {
.grid {
grid-template-columns: 1fr;
}
}
Notice how the mobile-first version adds complexity as the screen grows, while the desktop-first version removes complexity as the screen shrinks. The mobile-first approach produces cleaner, more maintainable CSS because each breakpoint only adds what is needed.
Width and Height Queries
Width-based queries are by far the most common type of media query. They test the viewport width (the browser's visible content area, excluding scrollbars).
min-width and max-width
/* Styles apply when viewport is 768px or wider */
@media (min-width: 768px) {
.sidebar {
display: block;
width: 250px;
}
}
/* Styles apply when viewport is 767px or narrower */
@media (max-width: 767px) {
.sidebar {
display: none;
}
.mobile-menu {
display: block;
}
}
/* Styles apply between 768px and 1023px (inclusive) */
@media (min-width: 768px) and (max-width: 1023px) {
.sidebar {
width: 200px;
}
}
Height Queries
Height queries are less common but useful for specific scenarios: sticky headers on short viewports, hero sections, and landscape mobile devices.
/* Short viewports: reduce padding on hero section */
@media (max-height: 500px) {
.hero {
padding: 2rem 1rem;
min-height: auto;
}
}
/* Tall viewports: use full-height hero */
@media (min-height: 800px) {
.hero {
min-height: 100vh;
display: flex;
align-items: center;
}
}
/* Landscape phones: combine width and height */
@media (max-height: 500px) and (orientation: landscape) {
.sticky-header {
position: relative; /* Un-stick on landscape phones */
}
}
Aspect Ratio
The aspect-ratio feature tests the ratio of viewport width to height. This is useful for optimizing layouts for ultrawide monitors or square-ish tablets.
/* Ultrawide monitors (21:9 or wider) */
@media (min-aspect-ratio: 21/9) {
.content {
max-width: 1400px;
margin: 0 auto;
}
}
/* Nearly square viewports (tablets in certain orientations) */
@media (min-aspect-ratio: 3/4) and (max-aspect-ratio: 4/3) {
.two-column {
grid-template-columns: 1fr; /* Stack on square-ish screens */
}
}
Orientation
/* Portrait: height is greater than or equal to width */
@media (orientation: portrait) {
.gallery {
grid-template-columns: repeat(2, 1fr);
}
}
/* Landscape: width is greater than height */
@media (orientation: landscape) {
.gallery {
grid-template-columns: repeat(4, 1fr);
}
}
Be cautious with orientation queries alone. A desktop browser that is taller than it is wide registers as portrait. Combine orientation with width or height queries for reliable device targeting.
User Preference Features
Modern media queries go beyond viewport dimensions. They can detect user preferences set at the operating system level, enabling you to build interfaces that respect the user's accessibility and aesthetic choices.
prefers-color-scheme: Dark Mode
The prefers-color-scheme feature detects whether the user has enabled dark mode in their operating system settings.
/* Define colors as custom properties for easy swapping */
:root {
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #111111;
--text-secondary: #555555;
--border-color: #e0e0e0;
--link-color: #2563eb;
--code-bg: #f0f0f0;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #0f0f0f;
--bg-secondary: #1a1a1a;
--text-primary: #eeeeee;
--text-secondary: #aaaaaa;
--border-color: #333333;
--link-color: #60a5fa;
--code-bg: #1e1e1e;
}
}
/* Use the variables throughout your CSS */
body {
background-color: var(--bg-primary);
color: var(--text-primary);
}
a {
color: var(--link-color);
}
code {
background: var(--code-bg);
padding: 0.2em 0.4em;
border-radius: 3px;
}
Adding a Manual Dark Mode Toggle
To combine the OS-level preference with a manual toggle, use a data attribute on the HTML element:
/* Default: follow OS preference (handled above) */
/* Manual override: user explicitly chose light mode */
[data-theme="light"] {
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #111111;
--text-secondary: #555555;
--border-color: #e0e0e0;
--link-color: #2563eb;
--code-bg: #f0f0f0;
}
/* Manual override: user explicitly chose dark mode */
[data-theme="dark"] {
--bg-primary: #0f0f0f;
--bg-secondary: #1a1a1a;
--text-primary: #eeeeee;
--text-secondary: #aaaaaa;
--border-color: #333333;
--link-color: #60a5fa;
--code-bg: #1e1e1e;
}
// JavaScript for the toggle button
const toggle = document.getElementById('theme-toggle');
toggle.addEventListener('click', () => {
const current = document.documentElement.dataset.theme;
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.dataset.theme = next;
localStorage.setItem('theme', next);
});
// On page load: restore saved preference
const saved = localStorage.getItem('theme');
if (saved) {
document.documentElement.dataset.theme = saved;
}
prefers-reduced-motion: Respecting Motion Sensitivity
Some users experience motion sickness, vertigo, or seizures from animations. The prefers-reduced-motion feature lets you disable or tone down animations for these users.
/* Full animations by default */
.card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}
.fade-in {
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Reduce or remove animations for users who prefer it */
@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;
}
}
The nuclear approach above (targeting *) removes all animations site-wide. A more nuanced approach is to reduce motion rather than eliminate it entirely:
@media (prefers-reduced-motion: reduce) {
.card {
transition: opacity 0.1s ease; /* Keep a subtle transition */
}
.card:hover {
transform: none; /* Remove movement, keep other hover effects */
}
.fade-in {
animation: fadeInSubtle 0.1s ease-out; /* Faster, no movement */
}
@keyframes fadeInSubtle {
from { opacity: 0; }
to { opacity: 1; }
}
}
prefers-contrast: High Contrast Mode
The prefers-contrast feature detects whether the user has requested increased or decreased contrast through their operating system.
/* Increase contrast for users who need it */
@media (prefers-contrast: more) {
:root {
--text-primary: #000000;
--text-secondary: #333333;
--bg-primary: #ffffff;
--border-color: #000000;
}
a {
text-decoration: underline; /* Always underline links */
text-decoration-thickness: 2px;
}
button {
border: 2px solid currentColor; /* Visible button borders */
}
.subtle-text {
color: var(--text-primary); /* No subtle/muted text */
}
}
/* Decrease contrast for users who prefer it */
@media (prefers-contrast: less) {
:root {
--text-primary: #444444;
--bg-primary: #fafafa;
--border-color: #e8e8e8;
}
}
prefers-color-gamut: Wide Color Displays
The color-gamut feature detects the color range of the user's display. Modern Apple devices and many newer monitors support the P3 color gamut, which is 25% wider than standard sRGB.
/* Default: sRGB colors */
:root {
--accent-blue: #3b82f6;
--accent-green: #22c55e;
--accent-red: #ef4444;
}
/* Wide gamut displays: use more vivid P3 colors */
@media (color-gamut: p3) {
:root {
--accent-blue: color(display-p3 0.2 0.5 1);
--accent-green: color(display-p3 0.1 0.8 0.3);
--accent-red: color(display-p3 1 0.2 0.2);
}
}
Interaction Features: hover and pointer
/* Only show hover effects on devices that support hover */
@media (hover: hover) {
.nav-link:hover {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
}
}
/* Touch devices: larger tap targets */
@media (pointer: coarse) {
button, .nav-link, a {
min-height: 44px;
min-width: 44px;
padding: 0.75rem 1rem;
}
.dropdown-item {
padding: 1rem; /* More spacing between tap targets */
}
}
/* Mouse/trackpad: can use smaller, denser UI */
@media (pointer: fine) {
.dropdown-item {
padding: 0.5rem 1rem;
}
.tooltip {
display: inline-block; /* Tooltips only work with hover */
}
}
Logical Operators: Combining Conditions
Media queries support logical operators to combine multiple conditions or negate them.
and: Both Conditions Must Be True
/* Screen width between 768px and 1023px */
@media (min-width: 768px) and (max-width: 1023px) {
.layout {
grid-template-columns: 1fr 1fr;
}
}
/* Landscape tablet or phone */
@media (max-width: 1024px) and (orientation: landscape) {
.hero {
min-height: 60vh; /* Reduce hero on landscape mobile */
}
}
/* Screen type AND width AND dark mode */
@media screen and (min-width: 768px) and (prefers-color-scheme: dark) {
.sidebar {
background: #0d0d0d;
border-right: 1px solid #222;
}
}
Comma (or): Either Condition Can Be True
/* Print OR narrow screen: stack the layout */
@media print, (max-width: 600px) {
.two-column {
grid-template-columns: 1fr;
}
}
/* Target multiple specific breakpoints */
@media (min-width: 768px) and (max-width: 1023px),
(min-width: 1280px) {
.special-layout {
/* Applies to tablets AND large desktops, but NOT small laptops */
}
}
In CSS Media Queries Level 4, you can also use the or keyword explicitly, but comma separation is more widely supported and produces the same result.
not: Negate a Condition
/* Everything except print */
@media not print {
.screen-only-element {
display: block;
}
}
/* Devices without hover capability (touch devices) */
@media not (hover: hover) {
.hover-menu {
display: none; /* Hide hover-dependent menus on touch */
}
.tap-menu {
display: block; /* Show touch-friendly alternative */
}
}
/* Not a narrow viewport */
@media not (max-width: 767px) {
/* Same as min-width: 768px */
.desktop-only {
display: block;
}
}
only: Prevent Legacy Browsers from Applying Styles
/* The only keyword was used to hide styles from old browsers
that did not understand media queries. It is rarely needed
in 2026 but you may see it in older codebases. */
@media only screen and (min-width: 768px) {
/* Identical to: @media screen and (min-width: 768px) */
}
Container Queries: Component-Level Responsiveness
Container queries are the biggest evolution in responsive design since media queries themselves. While media queries respond to the viewport, container queries respond to the size of a parent element. This makes components truly portable: a card component adapts to the space it is given, whether that is a narrow sidebar, a wide main content area, or a modal dialog.
Basic Container Query Syntax
/* Step 1: Define a containment context on the parent */
.card-container {
container-type: inline-size;
}
/* Step 2: Write styles that respond to the container width */
.card {
display: flex;
flex-direction: column;
padding: 1rem;
}
@container (min-width: 400px) {
.card {
flex-direction: row;
gap: 1.5rem;
}
.card .image {
width: 200px;
flex-shrink: 0;
}
}
@container (min-width: 700px) {
.card {
padding: 2rem;
}
.card .title {
font-size: 1.5rem;
}
}
container-type Values
/* inline-size: respond to the container's width (most common) */
.wrapper {
container-type: inline-size;
}
/* size: respond to both width AND height */
.wrapper {
container-type: size;
}
/* normal: no containment (default, cannot be queried) */
.wrapper {
container-type: normal;
}
Use inline-size in almost all cases. The size value also tracks height, which is useful for vertically scrolling containers, but it requires the container to have an explicit height. Without one, the browser cannot determine the container's block size and the query will not work.
Named Containers
When you have nested containers, you can name them to target specific ones:
/* Name the containers */
.page-layout {
container-type: inline-size;
container-name: page;
}
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
/* Shorthand: container property sets both name and type */
.main-content {
container: main / inline-size;
}
/* Query a specific container by name */
@container sidebar (min-width: 300px) {
.sidebar-widget {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
@container main (min-width: 800px) {
.article {
columns: 2;
column-gap: 2rem;
}
}
Container Query Units
Container queries introduce new length units relative to the container's dimensions:
/* Container query units */
.card-title {
/* cqw = 1% of container width */
font-size: clamp(1rem, 3cqw, 2rem);
}
.card-image {
/* cqh = 1% of container height (requires container-type: size) */
max-height: 50cqh;
}
/* Full list of container units:
cqw — 1% of container width
cqh — 1% of container height
cqi — 1% of container inline size
cqb — 1% of container block size
cqmin — smaller of cqi and cqb
cqmax — larger of cqi and cqb */
Practical Example: Adaptive Card Component
<!-- HTML -->
<div class="card-wrapper">
<article class="card">
<img src="thumb.jpg" alt="" class="card-img">
<div class="card-body">
<h3 class="card-title">Article Title</h3>
<p class="card-text">Brief description...</p>
<a href="#" class="card-link">Read more</a>
</div>
</article>
</div>
/* CSS */
.card-wrapper {
container-type: inline-size;
}
/* Default: narrow layout (sidebar, mobile) */
.card {
display: flex;
flex-direction: column;
background: var(--bg-secondary);
border-radius: 8px;
overflow: hidden;
}
.card-img {
width: 100%;
height: 180px;
object-fit: cover;
}
.card-body {
padding: 1rem;
}
/* Medium: horizontal card */
@container (min-width: 450px) {
.card {
flex-direction: row;
}
.card-img {
width: 200px;
height: auto;
min-height: 160px;
}
}
/* Wide: featured card with larger text */
@container (min-width: 700px) {
.card-img {
width: 300px;
}
.card-title {
font-size: 1.5rem;
}
.card-body {
padding: 1.5rem;
}
}
This card component works identically whether you place it in a full-width main content area or a narrow 300px sidebar. It responds to its container, not the viewport. This is the power of container queries: truly reusable, context-aware components.
The New Range Syntax
CSS Media Queries Level 4 introduced a range syntax that uses mathematical comparison operators instead of the min- and max- prefixes. It is more readable and eliminates the classic off-by-one-pixel confusion.
Range Syntax Examples
/* Old syntax → New range syntax */
@media (min-width: 768px) → @media (width >= 768px)
@media (max-width: 767px) → @media (width < 768px)
@media (min-width: 768px)
and (max-width: 1023px) → @media (768px <= width < 1024px)
/* More examples */
@media (width >= 1024px) {
.sidebar { display: block; }
}
@media (width < 768px) {
.mobile-nav { display: flex; }
}
/* Range with both bounds */
@media (768px <= width < 1024px) {
.tablet-layout { grid-template-columns: 1fr 1fr; }
}
/* Height range */
@media (height >= 800px) {
.hero { min-height: 100vh; }
}
/* Aspect ratio range */
@media (aspect-ratio >= 16/9) {
.video-container { width: 80%; }
}
Why the Range Syntax Is Better
/* The old off-by-one problem: */
/* What happens at exactly 768px? Both queries match! */
@media (max-width: 768px) { .a { color: red; } }
@media (min-width: 768px) { .a { color: blue; } }
/* Old workaround: use 767px for max-width */
@media (max-width: 767px) { .a { color: red; } }
@media (min-width: 768px) { .a { color: blue; } }
/* But 767.5px falls through both queries on high-DPI screens! */
/* Range syntax eliminates this entirely: */
@media (width < 768px) { .a { color: red; } }
@media (width >= 768px) { .a { color: blue; } }
/* Clean, unambiguous, no gaps, no overlaps */
Browser Support for Range Syntax
The range syntax is supported in Chrome 104+, Firefox 63+, Safari 16.4+, and Edge 104+. As of 2026, all current browsers support it. The older min-width/max-width syntax continues to work and will not be deprecated, so you can migrate gradually.
Responsive Design Patterns
Here are the most practical responsive design patterns using media queries, each with complete code you can copy directly.
Pattern 1: Mobile-First Responsive Grid
/* Cards that go from 1 → 2 → 3 → 4 columns */
.card-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
padding: 1rem;
}
@media (min-width: 540px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 900px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1200px) {
.card-grid {
grid-template-columns: repeat(4, 1fr);
max-width: 1280px;
margin: 0 auto;
}
}
Pattern 2: Responsive Typography
/* Fluid typography that scales smoothly between breakpoints */
html {
font-size: 16px; /* Base size */
}
h1 {
font-size: 1.75rem; /* 28px on mobile */
line-height: 1.2;
}
h2 {
font-size: 1.375rem; /* 22px on mobile */
line-height: 1.3;
}
@media (min-width: 768px) {
h1 { font-size: 2.25rem; } /* 36px on tablet */
h2 { font-size: 1.75rem; } /* 28px on tablet */
}
@media (min-width: 1024px) {
h1 { font-size: 3rem; } /* 48px on desktop */
h2 { font-size: 2rem; } /* 32px on desktop */
}
/* Modern alternative: clamp() for fluid sizing without media queries */
h1 {
font-size: clamp(1.75rem, 1rem + 3vw, 3rem);
}
h2 {
font-size: clamp(1.375rem, 1rem + 1.5vw, 2rem);
}
p {
font-size: clamp(1rem, 0.9rem + 0.3vw, 1.125rem);
line-height: 1.7;
max-width: 65ch; /* Optimal reading width */
}
Pattern 3: Fluid Grid with clamp() and Media Queries
/* Combine clamp() for fluid spacing with media queries for layout */
.page {
padding: clamp(1rem, 3vw, 3rem);
max-width: 1400px;
margin: 0 auto;
}
.section {
margin-bottom: clamp(2rem, 5vw, 5rem);
}
.content-grid {
display: grid;
grid-template-columns: 1fr;
gap: clamp(1rem, 2vw, 2rem);
}
@media (min-width: 768px) {
.content-grid {
grid-template-columns: 250px 1fr;
}
}
@media (min-width: 1024px) {
.content-grid {
grid-template-columns: 280px 1fr 250px;
}
}
Pattern 4: Print Stylesheet
/* Print-specific styles */
@media print {
/* Remove interactive and decorative elements */
header, footer, nav, .sidebar,
.no-print, button, .share-buttons,
.cookie-banner, .chat-widget {
display: none !important;
}
/* Reset colors for print */
body {
color: #000 !important;
background: #fff !important;
font-size: 12pt;
line-height: 1.5;
}
/* Ensure links are visible */
a {
color: #000 !important;
text-decoration: underline;
}
/* Show link URLs in print */
a[href^="http"]::after {
content: " (" attr(href) ")";
font-size: 0.8em;
color: #666;
}
/* Prevent page breaks inside important elements */
h1, h2, h3, h4, img, pre, blockquote {
break-inside: avoid;
}
/* Keep headings with their content */
h1, h2, h3, h4 {
break-after: avoid;
}
/* Full-width content */
.content {
max-width: 100%;
margin: 0;
padding: 0;
}
/* Force images to fit the page */
img {
max-width: 100% !important;
height: auto !important;
}
}
Real-World Examples
Example 1: Responsive Navigation
The classic hamburger-to-horizontal navigation pattern, built with media queries and no JavaScript for the layout (JavaScript is still needed for the menu toggle on mobile).
<!-- HTML -->
<header class="site-header">
<a href="/" class="logo">Logo</a>
<button class="menu-toggle" aria-label="Toggle menu">
<span></span><span></span><span></span>
</button>
<nav class="main-nav">
<a href="/features">Features</a>
<a href="/pricing">Pricing</a>
<a href="/docs">Docs</a>
<a href="/blog">Blog</a>
<a href="/contact" class="nav-cta">Contact</a>
</nav>
</header>
/* CSS */
.site-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
position: sticky;
top: 0;
background: var(--bg-primary);
z-index: 100;
border-bottom: 1px solid var(--border-color);
}
.logo {
font-weight: 700;
font-size: 1.25rem;
text-decoration: none;
}
/* Mobile: vertical dropdown nav */
.menu-toggle {
display: flex;
flex-direction: column;
gap: 4px;
background: none;
border: none;
cursor: pointer;
padding: 8px;
}
.menu-toggle span {
width: 24px;
height: 2px;
background: var(--text-primary);
transition: transform 0.3s, opacity 0.3s;
}
.main-nav {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
flex-direction: column;
padding: 1rem;
gap: 0.5rem;
}
.main-nav.open {
display: flex;
}
.main-nav a {
padding: 0.75rem 1rem;
text-decoration: none;
border-radius: 6px;
}
/* Desktop: horizontal nav */
@media (min-width: 768px) {
.menu-toggle {
display: none; /* Hide hamburger */
}
.main-nav {
display: flex !important; /* Always visible */
position: static;
flex-direction: row;
align-items: center;
padding: 0;
gap: 0.25rem;
border: none;
background: transparent;
}
.main-nav a {
padding: 0.5rem 0.75rem;
}
.nav-cta {
background: var(--link-color);
color: #fff !important;
border-radius: 6px;
margin-left: 0.5rem;
}
}
Example 2: Responsive Card Grid with Feature Queries
.card-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
padding: 1rem;
}
.card {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 12px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.card-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.card-content {
padding: 1.25rem;
flex: 1;
display: flex;
flex-direction: column;
}
.card-title {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.card-text {
color: var(--text-secondary);
font-size: 0.9rem;
flex: 1;
}
.card-footer {
padding: 1rem 1.25rem;
border-top: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
/* 2 columns on tablets */
@media (min-width: 600px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 3 columns on desktops */
@media (min-width: 960px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Feature card spans 2 columns on wider screens */
@media (min-width: 600px) {
.card.featured {
grid-column: span 2;
}
.card.featured {
flex-direction: row;
}
.card.featured .card-image {
width: 50%;
height: auto;
min-height: 280px;
}
}
/* Hover effects only for devices that support hover */
@media (hover: hover) {
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);
}
}
/* Respect motion preferences */
@media (prefers-reduced-motion: reduce) {
.card {
transition: none;
}
.card:hover {
transform: none;
}
}
Example 3: Responsive Dashboard Layout
.dashboard {
display: grid;
grid-template-areas:
"header"
"main";
grid-template-rows: auto 1fr;
min-height: 100vh;
}
.dashboard-header {
grid-area: header;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
.dashboard-sidebar {
display: none; /* Hidden on mobile */
}
.dashboard-main {
grid-area: main;
padding: 1rem;
}
.widget-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
.widget {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1.5rem;
}
/* Tablet: 2-column widget grid */
@media (min-width: 640px) {
.widget-grid {
grid-template-columns: repeat(2, 1fr);
}
.widget.wide {
grid-column: span 2;
}
}
/* Desktop: sidebar appears, 3-column widgets */
@media (min-width: 1024px) {
.dashboard {
grid-template-areas:
"sidebar header"
"sidebar main";
grid-template-columns: 260px 1fr;
}
.dashboard-sidebar {
display: flex;
grid-area: sidebar;
flex-direction: column;
padding: 1rem;
border-right: 1px solid var(--border-color);
gap: 0.25rem;
}
.widget-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Large desktop: 4-column widgets */
@media (min-width: 1400px) {
.widget-grid {
grid-template-columns: repeat(4, 1fr);
}
.widget.wide {
grid-column: span 2;
}
.widget.tall {
grid-row: span 2;
}
}
/* Dark mode adjustments */
@media (prefers-color-scheme: dark) {
.widget {
background: #1a1a2e;
border-color: #2a2a3e;
}
.dashboard-sidebar {
background: #0d0d1a;
}
}
Common Mistakes and Best Practices
Mistake 1: Using Device-Specific Breakpoints
/* BAD: targeting specific devices */
@media (width: 375px) { /* iPhone SE */ }
@media (width: 390px) { /* iPhone 14 */ }
@media (width: 412px) { /* Pixel 7 */ }
/* GOOD: content-driven breakpoints */
@media (min-width: 600px) {
/* Breakpoint where the layout naturally needs a second column */
}
@media (min-width: 960px) {
/* Breakpoint where a sidebar makes sense */
}
There are thousands of device widths. Targeting specific ones is a losing game. Set breakpoints where your content needs them.
Mistake 2: Overlapping Breakpoints
/* BAD: both apply at exactly 768px */
@media (max-width: 768px) {
.nav { flex-direction: column; }
}
@media (min-width: 768px) {
.nav { flex-direction: row; }
}
/* GOOD: use the range syntax to eliminate ambiguity */
@media (width < 768px) {
.nav { flex-direction: column; }
}
@media (width >= 768px) {
.nav { flex-direction: row; }
}
/* ALSO GOOD: use exclusive values with the old syntax */
@media (max-width: 767.98px) {
.nav { flex-direction: column; }
}
@media (min-width: 768px) {
.nav { flex-direction: row; }
}
Mistake 3: Not Testing with Real Content
/* This looks fine with short titles... */
.card-title {
font-size: 1.25rem;
white-space: nowrap; /* Dangerous! */
}
/* ...but breaks with real content on narrow screens.
Always test with realistic, varied-length content. */
/* GOOD: handle overflow gracefully */
.card-title {
font-size: 1.25rem;
overflow-wrap: break-word;
hyphens: auto;
}
Mistake 4: Forgetting the Viewport Meta Tag
<!-- Without this tag, mobile browsers render at ~980px
width and your media queries will not work as expected -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
This is the single most common reason media queries "do not work" on mobile. Without the viewport meta tag, the mobile browser uses its default viewport width (typically 980px), so your max-width: 768px queries never trigger.
Mistake 5: Using Too Many Breakpoints
/* BAD: too granular, hard to maintain */
@media (min-width: 320px) { ... }
@media (min-width: 375px) { ... }
@media (min-width: 414px) { ... }
@media (min-width: 480px) { ... }
@media (min-width: 640px) { ... }
@media (min-width: 768px) { ... }
@media (min-width: 834px) { ... }
@media (min-width: 1024px) { ... }
@media (min-width: 1280px) { ... }
@media (min-width: 1440px) { ... }
/* GOOD: 3-4 breakpoints cover most needs */
/* Base: mobile (no query) */
@media (min-width: 640px) { /* Tablet */ }
@media (min-width: 1024px) { /* Desktop */ }
@media (min-width: 1400px) { /* Large desktop (optional) */ }
Mistake 6: Not Using prefers-reduced-motion
/* BAD: animations with no opt-out */
.element {
animation: slideIn 0.5s ease-out;
}
/* GOOD: respect motion preferences */
.element {
animation: slideIn 0.5s ease-out;
}
@media (prefers-reduced-motion: reduce) {
.element {
animation: none;
/* or use a subtle fade instead */
animation: fadeIn 0.1s ease-out;
}
}
Best Practice: Organize Media Queries Near Their Components
/* APPROACH 1: Group by component (recommended) */
.card { ... }
.card-title { ... }
.card-body { ... }
@media (min-width: 768px) {
.card { ... }
.card-title { ... }
}
.sidebar { ... }
.sidebar-nav { ... }
@media (min-width: 768px) {
.sidebar { ... }
}
/* APPROACH 2: Group all queries at the end (acceptable) */
/* All component styles... */
@media (min-width: 768px) {
.card { ... }
.sidebar { ... }
.header { ... }
}
@media (min-width: 1024px) {
.card { ... }
.sidebar { ... }
}
Both approaches work. The first keeps related styles together, making it easier to find all styles for a component. The second gives a clearer picture of what changes at each breakpoint. Choose one approach and be consistent across your project.
Best Practice: Use Logical Properties for Internationalization
/* Physical properties (left-to-right only) */
.sidebar {
margin-left: 0;
padding-right: 2rem;
border-left: 2px solid blue;
text-align: left;
}
/* Logical properties (works in any writing direction) */
.sidebar {
margin-inline-start: 0;
padding-inline-end: 2rem;
border-inline-start: 2px solid blue;
text-align: start;
}
/* This becomes especially important in media queries */
@media (min-width: 768px) {
.sidebar {
/* Use inline-size instead of width for logical consistency */
inline-size: 280px;
}
}
Complete Media Feature Reference
Here is a concise reference of every commonly used media feature, grouped by category.
Viewport Dimensions
/* Width (most common) */
@media (min-width: 768px) { ... }
@media (max-width: 1023px) { ... }
@media (width >= 768px) { ... } /* Range syntax */
@media (768px <= width < 1024px) { ... } /* Range with bounds */
/* Height */
@media (min-height: 600px) { ... }
@media (height >= 600px) { ... }
/* Aspect ratio */
@media (aspect-ratio: 16/9) { ... }
@media (min-aspect-ratio: 1/1) { ... } /* Landscape or square */
@media (aspect-ratio >= 16/9) { ... } /* Wide screens */
/* Orientation */
@media (orientation: portrait) { ... }
@media (orientation: landscape) { ... }
Display Quality
/* Resolution (for retina/HiDPI assets) */
@media (min-resolution: 2dppx) { ... }
@media (min-resolution: 192dpi) { ... } /* Same as 2dppx */
@media (resolution >= 2dppx) { ... }
/* Color capabilities */
@media (color) { ... } /* Has color display */
@media (min-color: 8) { ... } /* 8+ bits per color */
@media (color-gamut: srgb) { ... }
@media (color-gamut: p3) { ... } /* Wide gamut */
@media (color-gamut: rec2020) { ... } /* Ultra wide gamut */
/* Dynamic range */
@media (dynamic-range: high) { ... } /* HDR display */
User Preferences
/* Color scheme */
@media (prefers-color-scheme: light) { ... }
@media (prefers-color-scheme: dark) { ... }
/* Motion */
@media (prefers-reduced-motion: reduce) { ... }
@media (prefers-reduced-motion: no-preference) { ... }
/* Contrast */
@media (prefers-contrast: more) { ... }
@media (prefers-contrast: less) { ... }
@media (prefers-contrast: no-preference) { ... }
/* Transparency (Windows) */
@media (prefers-reduced-transparency: reduce) { ... }
/* Forced colors (Windows High Contrast Mode) */
@media (forced-colors: active) { ... }
Interaction Capabilities
/* Primary pointing device */
@media (pointer: fine) { ... } /* Mouse, trackpad */
@media (pointer: coarse) { ... } /* Touch, stylus */
@media (pointer: none) { ... } /* No pointing device */
/* Hover capability */
@media (hover: hover) { ... } /* Can hover (mouse) */
@media (hover: none) { ... } /* Cannot hover (touch) */
/* Any pointing device (not just primary) */
@media (any-pointer: fine) { ... }
@media (any-hover: hover) { ... }
Frequently Asked Questions
What is the difference between min-width and max-width in media queries?
min-width applies styles when the viewport is at least the specified width (used in mobile-first design), while max-width applies styles when the viewport is at most the specified width (used in desktop-first design). For example, @media (min-width: 768px) targets tablets and larger, while @media (max-width: 767px) targets mobile devices. Mobile-first with min-width is the recommended approach because it starts with the simplest layout and progressively enhances for larger screens, resulting in cleaner CSS and better performance on mobile devices.
What are the most common responsive breakpoints in 2026?
The most widely used breakpoints in 2026 are: 480px (large phones), 768px (tablets), 1024px (small laptops), 1280px (desktops), and 1536px (large desktops). However, modern best practice is to set breakpoints based on where your content breaks rather than targeting specific devices. Use your browser's responsive design mode to resize the viewport and add a breakpoint wherever your layout stops looking good. This content-first approach produces more resilient designs than chasing device dimensions that change every year.
What is the difference between media queries and container queries?
Media queries respond to the viewport (browser window) dimensions, while container queries respond to the dimensions of a parent container element. Media queries use @media and are ideal for page-level layout changes. Container queries use @container and are ideal for reusable components that need to adapt based on the space they are given, regardless of the overall viewport size. For example, a card component placed in a narrow sidebar should look different from the same card in a wide main content area. Container queries make this possible without JavaScript. Both are supported in all major browsers as of 2023.
How do I implement dark mode with CSS media queries?
Use the prefers-color-scheme media feature to detect the user's operating system color preference. Write @media (prefers-color-scheme: dark) { ... } for dark mode styles. The best approach is to define CSS custom properties (variables) for your colors and swap them inside the media query: set light colors in :root, then override them inside the dark mode media query. For a manual toggle, add a data-theme attribute to the HTML element and use CSS attribute selectors alongside the media query. The prefers-color-scheme feature is supported in all major browsers.
What is the new media query range syntax and can I use it in production?
The new range syntax replaces min-width and max-width with mathematical comparison operators. Instead of @media (min-width: 768px) you write @media (width >= 768px), and instead of combining min-width and max-width you write @media (768px <= width < 1024px). The range syntax is more readable and eliminates the off-by-one-pixel confusion between min-width and max-width values. As of 2026, the range syntax is supported in Chrome 104+, Firefox 63+, Safari 16.4+, and Edge 104+, which means it is safe for production use. The older syntax continues to work and will not be deprecated.
Summary
CSS media queries are the backbone of responsive web design, and in 2026 they are more capable than ever. Here is a quick decision framework for using them effectively:
- Use mobile-first design — write base styles for the smallest screen, then use
min-widthqueries to enhance for larger screens. - Set breakpoints where your content breaks — not at arbitrary device widths. Three to four breakpoints cover most layouts.
- Use the range syntax for clearer, more readable queries:
@media (width >= 768px)instead of@media (min-width: 768px). - Use container queries for reusable components that need to adapt to their container, not the viewport.
- Respect user preferences — always handle
prefers-color-scheme,prefers-reduced-motion, andprefers-contrast. - Use
hoverandpointerqueries to serve appropriate interactions for touch and mouse users. - Combine with modern CSS — use
clamp()for fluid typography and spacing, CSS Grid'sauto-fill/minmax()for responsive grids without queries, and custom properties for theme switching. - Do not forget the viewport meta tag — without it, media queries will not work on mobile devices.
Media queries are not the only tool for responsive design. Modern CSS offers clamp(), viewport units, auto-fill/auto-fit grids, and container queries that reduce your reliance on breakpoints. The best responsive designs use media queries strategically for major layout shifts, and let intrinsic sizing handle the rest.
For quick CSS property references, keep our CSS Grid Cheat Sheet and CSS Flexbox Cheat Sheet bookmarked. To build responsive grid layouts visually, use the CSS Grid Generator. And for a deep dive into the layout systems that media queries control, read our CSS Grid Complete Guide and CSS Flexbox Complete Guide.