How do media queries work for responsive layouts?
The Short Answer
Media queries are CSS conditionals that apply styles only when certain conditions about the user's device or viewport are true — screen width, height, orientation, resolution, color scheme preference, and more. They're the foundation of responsive design: you write a base set of styles, then use @media rules to override them at different breakpoints. The browser evaluates these conditions in real-time, so styles update immediately when the user resizes their window or rotates their device.
Basic Syntax
A media query consists of an optional media type (screen, print, all) and one or more conditions (called media features) joined by logical operators. The styles inside the @media block only apply when the entire expression evaluates to true. You can combine conditions with and, negate with not, or provide alternatives with commas (which act as OR).
/* Basic width breakpoint */
@media (max-width: 768px) {
.sidebar { display: none; }
.content { width: 100%; }
}
/* Minimum width (mobile-first approach) */
@media (min-width: 768px) {
.container { max-width: 720px; }
}
/* Combining conditions with 'and' */
@media (min-width: 768px) and (max-width: 1024px) {
/* Tablet only — between 768px and 1024px */
.grid { grid-template-columns: repeat(2, 1fr); }
}
/* Media type + condition */
@media screen and (min-width: 1200px) {
.container { max-width: 1140px; }
}
/* Print styles */
@media print {
.no-print { display: none; }
body { font-size: 12pt; color: black; }
}
/* OR logic with commas */
@media (max-width: 600px), (orientation: portrait) {
/* Applies if EITHER condition is true */
.hero { height: 50vh; }
}
/* Negation */
@media not print {
/* Everything except print */
.screen-only { display: block; }
}
Common Breakpoint Patterns
While breakpoints should ideally be based on your content (where does the layout break?), most frameworks use device-category breakpoints as a starting point. The mobile-first approach starts with mobile styles as the default and adds complexity at larger sizes with min-width queries. This results in simpler mobile CSS and progressive enhancement for larger screens.
/* Mobile-first breakpoints (most common pattern) */
/* Base styles — mobile (no media query needed) */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
/* Small tablets and up */
@media (min-width: 640px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
/* Tablets and up */
@media (min-width: 768px) {
.grid { grid-template-columns: repeat(2, 1fr); gap: 24px; }
.sidebar { display: block; }
}
/* Laptops and up */
@media (min-width: 1024px) {
.grid { grid-template-columns: repeat(3, 1fr); }
}
/* Desktops and up */
@media (min-width: 1280px) {
.grid { grid-template-columns: repeat(4, 1fr); gap: 32px; }
.container { max-width: 1200px; margin: 0 auto; }
}
Modern Media Features
Beyond width and height, modern CSS supports media features for user preferences, device capabilities, and interaction modes. These let you adapt not just to screen size but to how the user prefers to experience your site — reduced motion, dark mode, high contrast, hover capability, and more.
/* Dark mode preference */
@media (prefers-color-scheme: dark) {
:root {
--background: #1a1a1a;
--foreground: #eaeaea;
}
}
/* Reduced motion — respect accessibility preferences */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* Hover capability — touch vs mouse devices */
@media (hover: hover) {
/* Device has a hover mechanism (mouse/trackpad) */
.card:hover { transform: translateY(-4px); }
}
@media (hover: none) {
/* Touch device — no hover, use tap states instead */
.card:active { transform: scale(0.98); }
}
/* High-resolution displays (retina) */
@media (min-resolution: 2dppx) {
.logo { background-image: url('logo@2x.png'); }
}
/* Orientation */
@media (orientation: landscape) {
.hero { height: 100vh; }
}
@media (orientation: portrait) {
.hero { height: 60vh; }
}
/* Range syntax (modern — Chrome 104+, Firefox 63+, Safari 16.4+) */
@media (768px <= width <= 1024px) {
/* Cleaner than min-width + max-width */
.tablet-layout { display: flex; }
}
Container Queries — The Modern Alternative
Media queries respond to the viewport size, but components often need to respond to their container's size instead. Container queries (supported in all modern browsers since 2023) let you style elements based on the size of their parent container — making truly reusable responsive components that adapt regardless of where they're placed in the layout.
/* Define a containment context */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Style based on container width (not viewport!) */
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
}
}
@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
}
/* The same card component adapts whether it's in:
- A full-width main content area (wide → horizontal layout)
- A narrow sidebar (narrow → stacked layout)
No viewport media query could handle both cases! */
Media Queries in JavaScript
The matchMedia API lets you evaluate media queries in JavaScript and react to changes. This is useful when you need to run different JavaScript logic at different breakpoints — not just different styles. The listener fires whenever the media query result changes, so you can update component behavior dynamically.
// Check a media query in JavaScript
const isMobile = window.matchMedia('(max-width: 768px)');
console.log(isMobile.matches); // true or false right now
// Listen for changes (e.g., window resize crosses breakpoint)
isMobile.addEventListener('change', (event) => {
if (event.matches) {
// Switched to mobile — maybe collapse a sidebar
closeSidebar();
} else {
// Switched to desktop — maybe open it
openSidebar();
}
});
// Check dark mode preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
prefersDark.addEventListener('change', (event) => {
applyTheme(event.matches ? 'dark' : 'light');
});
// React hook pattern
function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(
() => window.matchMedia(query).matches
);
useEffect(() => {
const mediaQuery = window.matchMedia(query);
const handler = (event: MediaQueryListEvent) => setMatches(event.matches);
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, [query]);
return matches;
}
Why Interviewers Ask This
Media queries are fundamental to responsive web development. Interviewers want to see that you understand mobile-first vs desktop-first approaches (and can articulate why mobile-first is preferred), know modern media features beyond just width (prefers-color-scheme, prefers-reduced-motion, hover), are aware of container queries and when they're better than media queries, can use matchMedia in JavaScript for responsive behavior, and understand how breakpoints should be chosen (content-driven, not device-driven).
Quick Revision Cheat Sheet
Mobile-first: Base styles for mobile, add complexity with min-width queries
Common breakpoints: 640px (sm), 768px (md), 1024px (lg), 1280px (xl)
prefers-color-scheme: Detect dark/light mode OS preference
prefers-reduced-motion: Disable animations for accessibility
Container queries: Respond to parent size, not viewport — truly reusable components
matchMedia(): Evaluate media queries in JS, listen for breakpoint changes