Media Queries: Syntax, Breakpoints & Features
What Are Media Queries?
Media queries are the cornerstone of responsive web design. They allow you to apply CSS rules conditionally based on characteristics of the device or viewport, such as screen width, orientation, color capability, or user preferences. Without media queries, every user would see the exact same layout regardless of whether they are on a widescreen monitor, a tablet, or a phone held in portrait mode. Media queries give you the power to adapt your design to fit any context.
The concept was introduced in CSS3 and has since evolved through Media Queries Level 4 and Level 5 specifications, adding powerful new features like user preference detection and range syntax. Understanding media queries thoroughly is essential for any developer building modern, responsive websites.
The @media Rule Syntax
A media query is written using the @media at-rule. It consists of an optional media type, zero or more media features enclosed in parentheses, and the CSS rules that should apply when the conditions are met. Here is the basic structure:
Example: Basic Media Query Structure
@media media-type and (media-feature: value) {
/* CSS rules that apply when conditions are true */
}
/* Real example: apply styles only on screens narrower than 768px */
@media screen and (max-width: 768px) {
.sidebar {
display: none;
}
.main-content {
width: 100%;
}
}
The @media keyword starts the query. The media type (like screen) is optional and defaults to all. The condition in parentheses is the media feature being tested. The curly braces contain the CSS rules that apply when the condition evaluates to true. You can place any valid CSS inside a media query block, including selectors, properties, animations, and even nested at-rules.
Media Types: all, screen, print, speech
Media types describe the general category of the device. CSS defines four media types, though in practice you will primarily use two of them:
- all -- Matches every device. This is the default when no type is specified. If you write
@media (max-width: 768px)without specifying a type, it implicitly means@media all and (max-width: 768px). - screen -- Matches devices with screens: desktops, laptops, tablets, phones, and any visual display. This is the most commonly specified type.
- print -- Matches printers and print preview mode. Use this to create printer-friendly stylesheets that remove navigation, change colors to black and white, and adjust layout for paper.
- speech -- Matches screen readers and speech synthesizers. This type is rarely used in practice because accessibility is typically handled through HTML semantics and ARIA attributes rather than CSS.
Example: Using Different Media Types
/* Applies to all devices (default) */
@media (max-width: 600px) {
body { font-size: 14px; }
}
/* Applies only to screens */
@media screen and (max-width: 600px) {
.mobile-menu { display: block; }
}
/* Applies only when printing */
@media print {
nav, footer, .sidebar { display: none; }
body { font-size: 12pt; color: #000; }
a { text-decoration: underline; }
a[href]::after { content: " (" attr(href) ")"; }
}
/* Applies to speech synthesizers */
@media speech {
.decorative { display: none; }
}
tv, handheld, projection, and tty have been deprecated. Modern CSS only recognizes all, screen, print, and speech. If you see these deprecated types in legacy code, they can be safely replaced with screen or removed.Media Features: Width, Height, Orientation & More
Media features are the conditions you test inside parentheses. They describe specific characteristics of the user agent, output device, or environment. Here are the most important ones:
Width and Height Features
These are the most commonly used media features. They test the width and height of the viewport (the visible area of the browser window):
Example: Width and Height Media Features
/* Exact width (rarely used) */
@media (width: 768px) {
/* Matches only when viewport is exactly 768px */
}
/* Maximum width: applies at this width and below */
@media (max-width: 768px) {
.container { padding: 0 15px; }
}
/* Minimum width: applies at this width and above */
@media (min-width: 768px) {
.container { max-width: 720px; margin: 0 auto; }
}
/* Height-based queries (less common but useful) */
@media (max-height: 500px) {
.hero { min-height: auto; padding: 20px 0; }
}
/* Combining min and max for a range */
@media (min-width: 768px) and (max-width: 1024px) {
/* Targets tablets specifically */
.grid { grid-template-columns: repeat(2, 1fr); }
}
Orientation
The orientation feature detects whether the viewport is wider than it is tall (landscape) or taller than it is wide (portrait):
Example: Orientation Media Feature
@media (orientation: portrait) {
.gallery { grid-template-columns: 1fr; }
}
@media (orientation: landscape) {
.gallery { grid-template-columns: repeat(3, 1fr); }
}
/* Combine with width for more specific targeting */
@media (orientation: landscape) and (max-height: 500px) {
/* Landscape phones: reduce header height */
.header { height: 50px; }
}
Aspect Ratio
The aspect-ratio feature tests the ratio of viewport width to height. This is useful for targeting ultrawide screens or specific device form factors:
Example: Aspect Ratio Media Feature
/* Ultrawide monitors (21:9 or wider) */
@media (min-aspect-ratio: 21/9) {
.content { max-width: 1400px; margin: 0 auto; }
}
/* Square-ish viewports */
@media (aspect-ratio: 1/1) {
.layout { grid-template-columns: 1fr; }
}
/* Tall narrow screens (portrait phones) */
@media (max-aspect-ratio: 3/4) {
.sidebar { position: fixed; bottom: 0; width: 100%; }
}
Logical Operators: and, or (comma), not, only
Logical operators let you combine or negate media features to create complex conditions. Mastering these operators is key to writing precise, efficient media queries.
The "and" Operator
The and operator combines multiple conditions that must all be true for the styles to apply:
Example: The "and" Operator
/* Both conditions must be true */
@media screen and (min-width: 768px) and (max-width: 1024px) {
/* Applies only on screens between 768px and 1024px */
.tablet-layout { display: grid; grid-template-columns: 1fr 1fr; }
}
/* Three conditions combined */
@media screen and (min-width: 1024px) and (orientation: landscape) and (min-height: 600px) {
.desktop-sidebar { display: block; width: 250px; }
}
The Comma (or) Operator
A comma between media queries acts as a logical OR. If any one of the comma-separated queries matches, the styles apply. In Media Queries Level 4, you can also use the explicit or keyword inside a single query:
Example: The Comma (OR) Operator
/* Either condition triggers the styles */
@media (max-width: 600px), (orientation: portrait) {
.flexible-layout { flex-direction: column; }
}
/* Print OR narrow screens */
@media print, (max-width: 768px) {
.sidebar { display: none; }
}
/* Multiple breakpoints that share the same styles */
@media (max-width: 480px), (min-width: 768px) and (max-width: 1024px) {
.special-layout { padding: 10px; }
}
The "not" Operator
The not keyword negates an entire media query. It inverts the result so the styles apply when the condition is false. Important: not applies to the entire query, not just a single feature:
Example: The "not" Operator
/* Applies to everything EXCEPT screens */
@media not screen {
body { font-family: serif; }
}
/* Applies when viewport is NOT narrow */
@media not all and (max-width: 600px) {
.desktop-only { display: block; }
}
/* With comma: "not" only applies to its own query */
@media not screen and (color), print {
/* "not" negates "screen and (color)" only */
/* "print" is a separate, un-negated query */
}
The "only" Keyword
The only keyword was designed to prevent older browsers (IE8 and below) from applying styles they could not properly interpret. Modern browsers handle it transparently. You may still see it in legacy code, but it is not necessary in new projects:
Example: The "only" Keyword
/* Legacy usage -- prevents old browsers from misreading the query */
@media only screen and (min-width: 768px) {
.container { max-width: 720px; }
}
/* In modern development, this is equivalent */
@media screen and (min-width: 768px) {
.container { max-width: 720px; }
}
Modern Range Syntax (Media Queries Level 4)
Media Queries Level 4 introduced a more intuitive range syntax using mathematical comparison operators. Instead of writing min-width and max-width, you can use >=, <=, >, and < operators. This syntax is now supported in all major browsers.
Example: Modern Range Syntax
/* Old syntax */
@media (min-width: 768px) { /* ... */ }
@media (max-width: 1024px) { /* ... */ }
@media (min-width: 768px) and (max-width: 1024px) { /* ... */ }
/* New range syntax -- same meaning, more readable */
@media (width >= 768px) { /* ... */ }
@media (width <= 1024px) { /* ... */ }
@media (768px <= width <= 1024px) { /* ... */ }
/* More examples */
@media (height > 600px) {
.tall-hero { min-height: 80vh; }
}
@media (400px <= width <= 800px) {
.mid-range { padding: 20px; }
}
/* Works with other features too */
@media (aspect-ratio >= 16/9) {
.cinematic { /* ultrawide styles */ }
}
(768px <= width <= 1024px) is much clearer than the old (min-width: 768px) and (max-width: 1024px). It reads naturally as "width between 768px and 1024px." Adopt this syntax for new projects, but be aware that older codebases may still use the min/max prefix style.Common Breakpoint Values and Naming Conventions
Breakpoints are the viewport widths at which your layout changes. While there are no universal "correct" breakpoints, the industry has settled on common values based on popular device sizes. Here are the most widely used breakpoint systems:
Example: Common Breakpoint Systems
/* Bootstrap 5 breakpoints */
/* xs: 0 - 575px (no media query needed, mobile-first default) */
/* sm: 576px and up */
/* md: 768px and up */
/* lg: 992px and up */
/* xl: 1200px and up */
/* xxl: 1400px and up */
/* Tailwind CSS breakpoints */
/* sm: 640px */
/* md: 768px */
/* lg: 1024px */
/* xl: 1280px */
/* 2xl: 1536px */
/* A practical custom system using CSS custom properties */
:root {
--bp-phone: 480px;
--bp-tablet: 768px;
--bp-desktop: 1024px;
--bp-wide: 1280px;
--bp-ultrawide: 1536px;
}
/* Note: CSS custom properties cannot be used inside media query
conditions. The values above are for documentation purposes.
Use the actual pixel values in your @media rules. */
@media (min-width: 480px) { /* phone landscape */ }
@media (min-width: 768px) { /* tablet */ }
@media (min-width: 1024px) { /* desktop */ }
@media (min-width: 1280px) { /* wide desktop */ }
@media (min-width: 1536px) { /* ultrawide */ }
Mobile-First vs Desktop-First
There are two fundamental approaches to writing media queries, and the choice between them affects your entire CSS architecture.
Mobile-First (min-width)
In mobile-first development, you write your base CSS for the smallest screens and then use min-width media queries to progressively add styles for larger screens. This is the recommended approach for most projects:
Example: Mobile-First Approach
/* Base styles: mobile (no media query needed) */
.container {
width: 100%;
padding: 0 16px;
}
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
.sidebar {
display: none;
}
/* Tablet and up */
@media (min-width: 768px) {
.container {
max-width: 720px;
margin: 0 auto;
padding: 0 24px;
}
.grid {
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
}
/* Desktop and up */
@media (min-width: 1024px) {
.container {
max-width: 960px;
}
.grid {
grid-template-columns: repeat(3, 1fr);
gap: 32px;
}
.sidebar {
display: block;
width: 250px;
}
}
Desktop-First (max-width)
In desktop-first development, your base CSS targets large screens and you use max-width media queries to override styles for smaller screens. This approach may make sense when redesigning an existing desktop site to be responsive:
Example: Desktop-First Approach
/* Base styles: desktop */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 32px;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 32px;
}
.sidebar {
display: block;
width: 300px;
}
/* Tablet and down */
@media (max-width: 1024px) {
.grid {
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.sidebar {
width: 200px;
}
}
/* Phone and down */
@media (max-width: 768px) {
.container {
padding: 0 16px;
}
.grid {
grid-template-columns: 1fr;
gap: 16px;
}
.sidebar {
display: none;
}
}
Combining Multiple Conditions
Real-world responsive design often requires combining multiple media features to precisely target specific scenarios. Here are patterns you will encounter frequently:
Example: Combining Multiple Conditions
/* Tablet in landscape mode */
@media (min-width: 768px) and (max-width: 1024px) and (orientation: landscape) {
.video-player { height: 60vh; }
}
/* Large screens that are not too tall (ultrawide monitors) */
@media (min-width: 1400px) and (max-height: 800px) {
.content { max-width: 1200px; }
.sidebar { position: sticky; top: 0; }
}
/* Either very small screens OR print */
@media (max-width: 480px), print {
.complex-table {
display: block;
}
.complex-table thead {
display: none;
}
.complex-table td {
display: block;
text-align: right;
}
.complex-table td::before {
content: attr(data-label);
float: left;
font-weight: bold;
}
}
Print Stylesheets with @media print
Print media queries let you optimize your page for paper output. When a user prints your page or uses print preview, these styles take effect. A good print stylesheet removes unnecessary UI elements, adjusts colors for readability on paper, and ensures content fits the printed page properly.
Example: Comprehensive Print Stylesheet
@media print {
/* Reset colors for print */
* {
color: #000 !important;
background: #fff !important;
box-shadow: none !important;
text-shadow: none !important;
}
/* Remove non-essential elements */
nav, .sidebar, .footer-social, .cookie-banner,
.back-to-top, .chat-widget, .ads {
display: none !important;
}
/* Ensure content flows properly */
body {
font-size: 12pt;
line-height: 1.5;
font-family: Georgia, "Times New Roman", serif;
}
/* Show full URLs for links */
a[href]::after {
content: " (" attr(href) ")";
font-size: 10pt;
color: #666 !important;
}
/* Don't show URL for internal or javascript links */
a[href^="#"]::after,
a[href^="javascript"]::after {
content: "";
}
/* Control page breaks */
h1, h2, h3 {
page-break-after: avoid;
}
img, table, figure {
page-break-inside: avoid;
}
/* Ensure images fit the page */
img {
max-width: 100% !important;
height: auto !important;
}
/* Set page margins */
@page {
margin: 2cm;
}
@page :first {
margin-top: 3cm;
}
}
prefers-reduced-motion for Accessibility
The prefers-reduced-motion media feature detects whether the user has requested that the system minimize non-essential motion. This is a critical accessibility feature for users with vestibular disorders, motion sensitivity, or who simply find animations distracting. Operating systems provide this setting (e.g., "Reduce motion" on macOS/iOS, "Show animations" on Windows).
Example: Respecting Reduced Motion Preferences
/* Default: include animations */
.hero-element {
animation: fadeInUp 0.8s ease-out;
}
.card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
}
/* Remove or reduce animations for users who prefer it */
@media (prefers-reduced-motion: reduce) {
.hero-element {
animation: none;
}
.card {
transition: none;
}
.card:hover {
transform: none;
}
/* Replace motion with instant state changes */
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Alternative: only add motion for users who are OK with it */
/* This "motion-first" approach is becoming preferred: */
.element {
/* No animation by default */
opacity: 1;
}
@media (prefers-reduced-motion: no-preference) {
.element {
animation: fadeIn 0.5s ease-out;
}
}
prefers-reduced-motion. Excessive animations can cause dizziness, nausea, and headaches for users with vestibular disorders. At minimum, wrap all non-essential animations and transitions inside a prefers-reduced-motion: no-preference check. Essential animations (like a loading spinner) can remain but should be simplified.prefers-color-scheme for Dark Mode
The prefers-color-scheme media feature detects whether the user prefers a light or dark color theme. This allows you to automatically match your website's appearance to the user's system preferences. It accepts two values: light and dark.
Example: Implementing Dark Mode with prefers-color-scheme
/* Define color scheme with CSS custom properties */
:root {
/* Light theme (default) */
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #1a1a1a;
--text-secondary: #666666;
--border-color: #e0e0e0;
--accent: #2563eb;
--accent-hover: #1d4ed8;
--shadow: rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
:root {
/* Dark theme */
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--border-color: #2d2d44;
--accent: #60a5fa;
--accent-hover: #93c5fd;
--shadow: rgba(0, 0, 0, 0.4);
}
}
/* Use the custom properties throughout your CSS */
body {
background-color: var(--bg-primary);
color: var(--text-primary);
}
.card {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
box-shadow: 0 2px 8px var(--shadow);
}
a {
color: var(--accent);
}
a:hover {
color: var(--accent-hover);
}
/* Handle images that might look bad in dark mode */
@media (prefers-color-scheme: dark) {
img:not([src*=".svg"]) {
filter: brightness(0.9);
}
}
hover and pointer Media Features
The hover and pointer media features let you detect the user's input mechanism. This is crucial for distinguishing between mouse-driven desktop experiences and touch-driven mobile experiences, regardless of screen size. A user on a large tablet may have a wide viewport but no hover capability.
Example: Detecting Input Mechanism
/* hover: can the primary input hover? */
/* "hover" = yes (mouse), "none" = no (touch) */
/* pointer: how precise is the primary input? */
/* "fine" = mouse/trackpad, "coarse" = finger/stylus, "none" = no pointer */
/* Only show hover effects for devices that support hover */
@media (hover: hover) {
.nav-link:hover {
background-color: var(--bg-secondary);
transform: translateY(-2px);
}
.tooltip-trigger:hover .tooltip {
opacity: 1;
visibility: visible;
}
}
/* For touch devices, make tap targets larger */
@media (pointer: coarse) {
.button {
min-height: 48px;
min-width: 48px;
padding: 12px 24px;
}
.nav-link {
padding: 16px 20px;
}
/* Remove hover-dependent UI that won't work on touch */
.hover-dropdown {
display: none;
}
}
/* Precise pointer: can use smaller, tighter layouts */
@media (pointer: fine) {
.compact-table td {
padding: 4px 8px;
}
.button-sm {
min-height: 32px;
padding: 4px 12px;
}
}
/* any-hover and any-pointer check ALL input devices */
/* Useful for hybrid devices like laptops with touchscreens */
@media (any-hover: hover) {
/* At least one input can hover */
}
@media (any-pointer: fine) {
/* At least one input has fine precision */
}
hover and pointer media features give you a much more accurate picture of the user's input capabilities.Organizing Media Queries: Strategies
How you organize media queries in your codebase has a significant impact on maintainability. There are two main strategies, and most professional projects use a combination of both.
Strategy 1: Component-Level Media Queries
Place media queries right next to the component they affect. This keeps all styles for a component together and makes it easy to find responsive rules:
Example: Component-Level Media Queries
/* ========= Header Component ========= */
.header {
padding: 10px 16px;
display: flex;
align-items: center;
}
.header__logo {
width: 120px;
}
.header__nav {
display: none; /* Hidden on mobile */
}
.header__menu-btn {
display: block;
}
@media (min-width: 768px) {
.header {
padding: 16px 32px;
}
.header__logo {
width: 160px;
}
.header__nav {
display: flex;
gap: 24px;
}
.header__menu-btn {
display: none;
}
}
/* ========= Card Component ========= */
.card {
padding: 16px;
border-radius: 8px;
}
@media (min-width: 768px) {
.card {
padding: 24px;
border-radius: 12px;
}
}
Strategy 2: Separate Breakpoint Files
Collect all media queries for a specific breakpoint in a dedicated file. This gives you a clear overview of everything that changes at each breakpoint:
Example: Separate Breakpoint File Structure
/* File: styles/base.css -- mobile-first base styles */
/* File: styles/tablet.css -- @media (min-width: 768px) */
/* File: styles/desktop.css -- @media (min-width: 1024px) */
/* In your main stylesheet or HTML: */
/* <link rel="stylesheet" href="styles/base.css"> */
/* <link rel="stylesheet" href="styles/tablet.css" media="(min-width: 768px)"> */
/* <link rel="stylesheet" href="styles/desktop.css" media="(min-width: 1024px)"> */
/* Note: The browser downloads all stylesheets regardless of the
media attribute, but it only applies the matching ones and
won't block rendering for non-matching sheets. */
Practical Responsive Example with Multiple Breakpoints
Let us build a complete responsive page layout using everything we have learned. This example demonstrates a real-world responsive design pattern with a header, main content area, sidebar, and footer:
Example: Complete Responsive Layout
/* ===== CSS Reset and Base Styles (Mobile First) ===== */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--color-bg: #f8f9fa;
--color-surface: #ffffff;
--color-text: #212529;
--color-primary: #0d6efd;
--radius: 8px;
--shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: var(--color-bg);
color: var(--color-text);
line-height: 1.6;
}
/* ===== Mobile Layout (default) ===== */
.page-header {
background: var(--color-surface);
padding: 12px 16px;
box-shadow: var(--shadow);
position: sticky;
top: 0;
z-index: 100;
}
.nav-list {
display: none; /* Hidden on mobile, toggled via JS */
list-style: none;
}
.mobile-menu-toggle {
display: block;
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
}
.page-layout {
padding: 16px;
}
.main-content {
margin-bottom: 24px;
}
.card-grid {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
.sidebar {
background: var(--color-surface);
padding: 16px;
border-radius: var(--radius);
}
/* ===== Tablet: 768px and up ===== */
@media (min-width: 768px) {
.page-header {
padding: 16px 32px;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-list {
display: flex;
gap: 24px;
}
.mobile-menu-toggle {
display: none;
}
.page-layout {
padding: 24px 32px;
}
.card-grid {
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
}
/* ===== Desktop: 1024px and up ===== */
@media (min-width: 1024px) {
.page-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: 32px;
max-width: 1200px;
margin: 0 auto;
padding: 32px;
}
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
.sidebar {
position: sticky;
top: 80px;
align-self: start;
}
}
/* ===== Wide Desktop: 1280px and up ===== */
@media (min-width: 1280px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
.page-layout {
max-width: 1400px;
grid-template-columns: 1fr 350px;
}
}
/* ===== Dark Mode ===== */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #121212;
--color-surface: #1e1e1e;
--color-text: #e0e0e0;
--color-primary: #60a5fa;
--shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
}
/* ===== Print ===== */
@media print {
.page-header, .sidebar, .mobile-menu-toggle {
display: none;
}
.page-layout {
display: block;
padding: 0;
}
.card-grid {
display: block;
}
}
/* ===== Accessibility ===== */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* ===== Touch Optimization ===== */
@media (pointer: coarse) {
.nav-list a {
padding: 12px 16px;
min-height: 48px;
display: flex;
align-items: center;
}
button, .button {
min-height: 48px;
min-width: 48px;
}
}
Practice Exercise
Build a responsive product listing page that uses a mobile-first approach. Start with a single-column layout for phones. At 640px, switch to a two-column grid. At 1024px, add a sidebar with filter options and display three product columns. At 1280px, show four product columns. Include a print stylesheet that removes the sidebar and shows products in a clean list. Add prefers-color-scheme: dark support with appropriate dark background and light text colors. Use prefers-reduced-motion: reduce to disable any hover animations. Finally, use the pointer: coarse query to increase the size of filter checkboxes and buttons for touch devices. Test your design by resizing the browser at every stage to make sure there are no awkward in-between states where the layout breaks.