CSS Media Queries
Apply different styles at different screen sizes using @media rules — the backbone of responsive CSS.
1. Introduction
A CSS media query is a conditional rule that applies styles only when certain conditions are true — like "the screen is wider than 768px" or "the user prefers dark mode".
Media queries are the main mechanism for making layouts adapt to different screen sizes. They let you say:
- "On screens wider than 768px, show 2 columns"
- "On screens wider than 1200px, show 3 columns"
- "In print mode, hide the navigation"
- "If the user prefers dark mode, use a dark colour scheme"
2. Theory
Basic Syntax
@media media-type and (condition) {
/* CSS rules that apply when condition is true */
}
Media Types
| Type | Applies to |
|---|---|
screen | Computer screens, phones, tablets (default) |
print | Print preview and printing |
all | All media types (default if omitted) |
@media screen and (min-width: 768px) { }
@media print { .no-print { display: none; } }
Width-Based Media Features
/* min-width: applies at THIS width and ABOVE */
@media (min-width: 768px) { /* tablet and wider */ }
@media (min-width: 1024px) { /* laptop and wider */ }
@media (min-width: 1280px) { /* desktop and wider */ }
/* max-width: applies at THIS width and BELOW */
@media (max-width: 767px) { /* mobile only */ }
@media (max-width: 1023px) { /* tablet and smaller */ }
/* Range: between two widths */
@media (min-width: 768px) and (max-width: 1023px) { /* tablet only */ }
/* Modern range syntax (newer browsers) */
@media (width >= 768px) { }
@media (768px <= width <= 1023px) { }
Common Breakpoint System
/* Mobile first: base styles for mobile */
/* No media query needed for smallest screens */
/* Tablet */
@media (min-width: 640px) { } /* sm */
@media (min-width: 768px) { } /* md */
/* Laptop */
@media (min-width: 1024px) { } /* lg */
@media (min-width: 1280px) { } /* xl */
/* Desktop */
@media (min-width: 1536px) { } /* 2xl */
Non-Width Media Features
/* Orientation */
@media (orientation: landscape) { }
@media (orientation: portrait) { }
/* Resolution (for retina/HiDPI displays) */
@media (-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
/* Use 2x images */
}
/* Hover capability */
@media (hover: hover) {
/* User has a mouse — safe to use hover effects */
}
@media (hover: none) {
/* Touch device — no reliable hover */
}
/* Dark mode preference */
@media (prefers-color-scheme: dark) {
body { background: #0f172a; color: #f8fafc; }
}
/* Reduced motion (accessibility) */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* Print styles */
@media print {
.no-print, nav, .sidebar, .ads { display: none; }
body { font-size: 12pt; color: black; }
a::after { content: " (" attr(href) ")"; } /* show URLs */
}
Combining Media Features
/* AND — both must be true */
@media (min-width: 768px) and (orientation: landscape) { }
/* OR — at least one must be true (comma = or) */
@media (max-width: 600px), (orientation: portrait) { }
/* NOT — invert the query */
@media not screen { }
@media not (min-width: 768px) { } /* below 768px */
3. Real World Example
A typical responsive website might have these breakpoints:
- Mobile (<768px): Single column, hamburger menu, stacked cards, full-width images
- Tablet (768px–1023px): Two columns, horizontal nav, card grids with 2 per row
- Desktop (1024px+): Three+ columns, full navigation, sidebar visible, large hero images
Additionally, modern sites add:
prefers-color-scheme: darkfor automatic dark modeprefers-reduced-motion: reducefor accessibilityprintstyles for clean printing
4. Code Example
/* ===== MOBILE-FIRST RESPONSIVE LAYOUT ===== */
/* --- BASE STYLES (mobile, no media query) --- */
.page {
display: grid;
grid-template-areas: "header" "main" "sidebar" "footer";
grid-template-columns: 1fr;
gap: 0;
}
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
height: 56px;
background: #1e293b;
}
.nav-links {
display: none; /* hidden on mobile */
}
.nav-toggle {
display: block; /* hamburger button visible on mobile */
}
.card-grid {
display: grid;
grid-template-columns: 1fr; /* single column on mobile */
gap: 16px;
padding: 16px;
}
.hero {
padding: 48px 16px;
text-align: center;
}
h1 { font-size: 1.75rem; }
/* --- TABLET (768px+) --- */
@media (min-width: 768px) {
.page {
grid-template-areas:
"header header"
"sidebar main "
"footer footer";
grid-template-columns: 240px 1fr;
}
.nav-links { display: flex; gap: 8px; } /* show links */
.nav-toggle { display: none; } /* hide hamburger */
.card-grid {
grid-template-columns: repeat(2, 1fr); /* 2 columns */
padding: 24px;
gap: 24px;
}
.hero { padding: 80px 32px; }
h1 { font-size: 2.5rem; }
}
/* --- DESKTOP (1024px+) --- */
@media (min-width: 1024px) {
.card-grid {
grid-template-columns: repeat(3, 1fr); /* 3 columns */
}
.hero { padding: 120px 24px; }
h1 { font-size: 3rem; }
}
/* --- LARGE DESKTOP (1280px+) --- */
@media (min-width: 1280px) {
.card-grid { gap: 32px; }
.container { max-width: 1280px; }
}
/* --- DARK MODE --- */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0f172a;
--color-text: #f1f5f9;
--color-border: #334155;
}
body {
background: var(--color-bg);
color: var(--color-text);
}
}
/* --- REDUCED MOTION --- */
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; animation: none !important; }
}
/* --- PRINT --- */
@media print {
.navbar, .sidebar, .footer, .btn { display: none; }
body { font-size: 11pt; color: black; background: white; }
.container { max-width: 100%; }
h1 { font-size: 20pt; }
a::after { content: " (" attr(href) ")"; font-size: 10pt; }
}
5. Code Breakdown
- Mobile-first approach
- The base CSS (no media query) is for mobile. The nav links are hidden (
display: none), hamburger is visible (display: block), single-column grid. Then@media (min-width: 768px)adds the tablet version: shows nav links, hides hamburger, adds sidebar column. grid-template-areaschanging in media query- On mobile: four-row single-column template. On tablet+: two-column with sidebar. The grid items (header, sidebar, main, footer) just follow wherever their named area is placed. Clean responsive redesign with minimal CSS.
prefers-color-scheme: dark- Automatically switches to dark mode if the user's OS is set to dark mode. By using CSS variables (
--color-bg, etc.) defined in:root, you only override the variable values, not every individual property. a::after { content: " (" attr(href) ")"; }in print- When printing, links aren't clickable. This
::afterpseudo-element appends the href URL in parentheses after each link, making them useful in printed form.attr(href)reads the actual href attribute value.
6. Common Mistakes
-
Desktop-first with
max-widthqueries. Writing@media (max-width: 767px)to fix mobile means you are undoing desktop styles. Mobile-first withmin-widthis cleaner. - Too many breakpoints. 3–4 breakpoints are usually enough. Adding a breakpoint for every popular device creates a maintenance nightmare.
-
Forgetting
prefers-reduced-motion. Animations can trigger vestibular disorders. Always add this accessibility media query. - Not including media queries in a logical order. In mobile-first CSS: base styles first, then increasingly large breakpoints. In desktop-first: base styles first, then decreasingly small breakpoints. Mixing the order causes specificity issues.
- Using JavaScript for basic responsive behaviour. Things like column count changes and hiding/showing elements should be CSS media queries, not JS. JS is for complex interactive changes.
7. Best Practices
- Write media queries at the end of each component's CSS, not all in one separate block — keeps related code together.
- Use mobile-first (
min-width) queries — less override, cleaner code. - Keep breakpoints in CSS variables so they are defined in one place.
- Always include
prefers-reduced-motionfor animations. - Add dark mode support with
prefers-color-scheme: darkand CSS variables. - Test media queries by resizing in DevTools — use the responsive mode, not device presets alone.
8. Practice Exercise
- Create a card grid that shows 1 card on mobile, 2 on tablet, 3 on desktop using media queries.
- Create a navigation that shows links horizontally on desktop but hidden on mobile (add a visible hamburger placeholder).
- Add a
prefers-color-scheme: darkquery that changes the page background and text colour. - Add a
@media printblock that hides the navigation and changes the font. - Add a
prefers-reduced-motionblock that disables all transitions.
9. Assignment
Build a fully responsive website with all major media queries:
- Mobile-first base styles (single column, stacked nav)
- Tablet breakpoint (768px): two-column grid, horizontal nav
- Desktop breakpoint (1024px): three-column grid, sidebar appears
- Dark mode (
prefers-color-scheme) - Reduced motion (
prefers-reduced-motion) - Print styles (hide nav, show URLs after links)
- Test all breakpoints in DevTools
10. Interview Questions
Q1: What is a CSS media query?
Answer: A media query is an @media rule that applies CSS styles conditionally — only when certain criteria are met, such as the viewport width exceeding a breakpoint, the user's colour scheme preference, or the output being printed.
Q2: What is the difference between min-width and max-width in media queries?
Answer: min-width applies styles at that width and above (mobile-first approach). max-width applies styles at that width and below (desktop-first). Mobile-first with min-width is the recommended approach because you progressively enhance for larger screens rather than overriding desktop styles.
Q3: What is prefers-reduced-motion and why should you implement it?
Answer: prefers-reduced-motion detects when the user has requested reduced animation (via OS accessibility settings). Some users experience vertigo, dizziness, or discomfort from animation. Implementing this media query by reducing or eliminating animations makes your site accessible to these users and is considered a baseline accessibility requirement.
Q4: What is prefers-color-scheme and how do you implement dark mode?
Answer: prefers-color-scheme: dark detects if the user's OS is in dark mode. To implement dark mode, define all colours as CSS custom properties in :root, then override those variables inside the dark media query. This way, all components automatically update without individually targeting each element.
Q5: How do you add print styles to a webpage?
Answer: Use @media print { }. Inside, hide irrelevant elements (navbars, sidebars, ads) with display: none, set a readable font size in points (e.g. 12pt), set text colour to black, and optionally show URLs after links using a::after { content: " (" attr(href) ")"; }.
11. Additional Resources
- MDN — Media Queries (search "MDN using media queries")
- MDN — prefers-color-scheme (search "MDN prefers-color-scheme")
- MDN — prefers-reduced-motion (search "MDN prefers-reduced-motion")
- web.dev — Responsive design patterns (search "web.dev responsive patterns")
- CSS-Tricks — A Complete Guide to CSS Media Queries (search this title)