Responsive Design and Media Queries
Your page is about to learn to check the weather and get dressed for it.
Pull out your phone and visit a few of your favorite websites. Then visit the same sites on a laptop. Same content, but the layout is different, isn't it? The menu collapses into a button, three columns become one, images shrink to fit.
That's responsive design: one page, one set of HTML, that adapts to whatever screen it's on. And these days it isn't optional. For most websites, more than half of visitors are on a phone. A site that only looks good on a laptop is broken for most of its audience.
Getting dressed for the weather
Think about how you get dressed in the morning. You check the weather, then decide:
- Hot day? T-shirt.
- Cool morning? Add a light jacket.
- Freezing? Add a coat and scarf on top.
You don't own three completely different bodies for three kinds of weather. You're the same person, putting on different layers. A responsive page works the same way: same HTML, different layers of CSS depending on the "weather," which here means the screen size.
The tool that checks the weather is called a media query.
First, the viewport tag
Before any of this works, the page needs one line in its <head> that you met back in Meta Tags and SEO:
<meta name="viewport" content="width=device-width, initial-scale=1">Without it, phones pretend to be a desktop screen about 980px wide, shrink your page to fit, and ignore your responsive CSS entirely. With it, the phone reports its real width, and your media queries can respond. If your media queries "don't work on my phone," check this tag first.
Fluid first
Before you reach for media queries, remember that a lot of responsiveness comes free when you avoid rigid sizes:
img,
video {
max-width: 100%;
height: auto;
}
.container {
max-width: 70rem;
margin: 0 auto;
padding: 0 1rem;
}- Images that never grow wider than their container.
- A content column with a maximum width, not a fixed one, so it shrinks gracefully on small screens.
- Percentages,
frunits, and flexbox or grid wrapping, all from earlier lessons, that bend instead of break.
A well-built fluid layout needs surprisingly few media queries. Remember the auto-fit card grid from CSS Grid in Practice? No media queries at all.
Media queries: checking the weather
@media (min-width: 48rem) {
.card-row {
flex-direction: row;
}
}Read it as: "If the screen is at least 48rem wide, apply these rules." Everything inside the curly braces only kicks in when the condition is true. When the screen is narrower, it's as if those rules don't exist.
Mobile-first: start with the T-shirt
Here's the approach professionals use, and it's worth getting into the habit right away.
Write your base CSS for the smallest screen first: one column, simple, stacked. That's the T-shirt, the outfit that works for everyone. Then use min-width media queries to add layers as screens get bigger:
/* Base: phones. One column. */
.page {
display: grid;
gap: 1rem;
}
/* Tablets and up: add a sidebar. */
@media (min-width: 48rem) {
.page {
grid-template-columns: 14rem 1fr;
}
}
/* Large screens: add more breathing room. */
@media (min-width: 75rem) {
.page {
gap: 2rem;
}
}Why mobile first, instead of designing for desktop and then stripping things away with max-width queries?
- Simpler CSS. Small screens get the simplest layout, so the base styles stay simple. You only add complexity, you rarely have to undo it.
- Better for phones. Phones, often on slow connections, get the leanest styles, without first loading the complicated desktop layout and then overriding it.
- Better thinking. Starting small forces you to decide what really matters. A phone screen has no room for clutter.
Adding a coat when it's cold is easier than walking around in one all year and taking it off when it's warm.
Choosing breakpoints
The widths where your layout changes are called breakpoints. Beginners often ask, "What are the right breakpoints for iPhone and iPad?"
Here's the honest answer: don't design for specific devices. There are thousands of screen sizes, and new ones every year. Instead, let your content decide. Start with a narrow window and slowly drag it wider. The moment the layout starts to look awkward (lines too long, lots of empty space, cards stretched too wide), that's a breakpoint. Add a media query there.
Using rem for breakpoints (like 48rem instead of 768px) is a nice touch, because if someone has set a larger default text size, the layout switches to the roomier arrangement sooner, exactly when they need it.
Fluid type with clamp()
Headings that look perfect on a laptop are often enormous on a phone. You could fix that with media queries. Or:
h1 {
font-size: clamp(2rem, 1.5rem + 3vw, 3.5rem);
}clamp(minimum, preferred, maximum) sets a value that grows smoothly with the screen but never goes below the minimum or above the maximum. The heading scales with the window width (thanks to that vw from Units), but it will never be smaller than 2rem or larger than 3.5rem. Like a thermostat with a floor and a ceiling.
More than width: user preferences
Media queries can check more than screen size. They can check what the visitor prefers:
/* The visitor's device is set to dark mode */
@media (prefers-color-scheme: dark) {
body {
background: #111827;
color: #e5e7eb;
}
}
/* The visitor has asked for less motion. (Transitions and Animations explains
why this one needs !important.) */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* The device has a mouse that can hover */
@media (hover: hover) {
.card:hover {
transform: translateY(-4px);
}
}These are small acts of respect. Someone who gets motion sickness from animations has told their device so; honor it. You'll use prefers-reduced-motion properly in Transitions and Animations, and dark mode becomes much easier with CSS Variables.
Container queries, briefly
Media queries check the size of the whole screen. But sometimes a component cares about the size of its own container. A card might sit in a wide main column on one page and a narrow sidebar on another. Container queries let a component respond to its container instead:
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 30rem) {
.card {
display: flex;
}
}You don't need these yet, but they're well supported now and increasingly common in professional code, so it's good to recognize them.
Testing
Two ways to test, and you should use both:
- Drag your browser window narrower and wider, and watch the layout respond. Or use your browser's developer tools' device mode, which simulates phone screens.
- Use a real phone. Simulators are good, but nothing beats holding the page in your hand, tapping buttons with your actual thumb.
Try it
The preview below is only as wide as this page's content column. To see the media query switch, make your whole browser window narrower or wider and watch the preview respond. Or change the 40rem value until the layout flips.
CSS
Preview
Try it yourself
- Change
40remto20rem, then to80rem, and run it each time. At which value does the preview switch layouts? - Add a
prefers-color-scheme: darkmedia query that gives the page a dark background. If your device is set to dark mode, you'll see it right away. - Change the
clamp()minimum and maximum values and resize your window to see the heading scale smoothly.
Check your understanding
Up next
Your pages now dress for any weather. Next, we'll make your styles far easier to manage, and set up an entire color theme in a few lines, with CSS Variables.
