Responsive Design and Media Queries
Prerequisites: CSS Grid basics
Responsive design means one page that adapts to any screen. It rests on three things:
- The viewport meta tag in the HTML
(
<meta name="viewport" content="width=device-width, initial-scale=1">). - Fluid layouts — Flexbox, Grid, percentages,
max-width, and images withmax-width: 100%. - Media queries for the changes fluid layout can't handle.
Media queries
.nav { display: flex; flex-direction: column; }
@media (width >= 48rem) {
.nav { flex-direction: row; }
}
@media (width >= 48rem) and (width < 64rem) {
.sidebar { display: none; }
}
@media print {
.nav, .ads { display: none; }
a[href^="http"]::after { content: " (" attr(href) ")"; }
}
- The range syntax (
width >= 48rem) works in all current browsers. You'll also see the older(min-width: 48rem)form, which means the same thing. - Media queries in
em/remrespect the user's font size setting. printstyles hide navigation and show link URLs on paper.
Mobile first
Write the base styles for small screens, then add complexity for
larger ones with min-width/>= queries. Small-screen styles are
usually simpler, so you override less.
Choosing breakpoints
Don't target specific devices — there are thousands. Resize the browser
and add a breakpoint where your content starts to look wrong. Often
two or three are enough, and repeat(auto-fit, minmax(...)) grids,
flex-wrap, and clamp() remove the need for many.
User preference queries
@media (prefers-color-scheme: dark) {
:root { --bg: #111; --text: #eee; }
}
@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;
}
}
@media (prefers-contrast: more) {
:root { --border: #000; }
}
@media (hover: hover) and (pointer: fine) {
.card:hover { transform: translateY(-2px); }
}
prefers-color-schemefollows the operating system's dark mode.prefers-reduced-motion— people with vestibular disorders or migraines turn off animations. Respect it.hover/pointer— detect a mouse versus a touch screen, instead of guessing from screen width.forced-colors: activedetects Windows High Contrast mode.
Testing a media query from JavaScript
<script>
console.log("wide:", matchMedia("(width >= 48rem)").matches);
console.log("print:", matchMedia("print").matches);
console.log("reduced motion:", matchMedia("(prefers-reduced-motion: reduce)").matches);
</script>
wide: true
print: false
reduced motion: false
(Run in a desktop browser window about 1280 pixels wide, with default settings.)
Practice
Make .cards one column by default, two columns from 40rem, and three
from 64rem, using Grid.
Show solution
.cards {
display: grid;
gap: 1rem;
}
@media (width >= 40rem) {
.cards { grid-template-columns: repeat(2, 1fr); }
}
@media (width >= 64rem) {
.cards { grid-template-columns: repeat(3, 1fr); }
}
Or with no media queries at all:
grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));