Responsive Images and Media Queries
Make images and layouts adapt to any screen: fluid images, the srcset and sizes attributes, the picture element, and media queries with mobile-first breakpoints. Runnable code, a try-it section and a quiz.
Key takeaways
- max-width: 100% keeps an image from overflowing its container
- srcset and sizes let the browser pick the best-sized image for the screen
- The <picture> element swaps whole images at different breakpoints (art direction)
- Media queries apply CSS only when a condition like a screen width is met
- Mobile-first means writing base styles for phones, then enhancing with min-width queries
One page, every screen
People visit websites on tiny phones, tablets, laptops and huge desktop monitors. A good page reshapes itself to fit each one. Two tools make this happen: responsive images that resize and even swap themselves, and media queries that apply different CSS at different screen sizes. This lesson goes deeper than Responsive Web Design Basics, so start there if viewport and % widths are new to you.
Step 1: stop images overflowing
By default an image displays at its natural pixel size. Put a 2000px-wide photo in a phone-width column and it spills off the screen, forcing horizontal scrolling. The fix is one rule:
img {
max-width: 100%;
height: auto;
}
max-width: 100% means "never wider than your container," so the image shrinks on small screens but won't blow up past its natural size on big ones. height: auto keeps the proportions correct so it never looks squashed. This single rule solves the most common image problem on the web.
Step 2: srcset β let the browser choose
A fluid image still downloads the full huge file even on a phone, wasting data. The srcset attribute offers the browser several sizes and lets it pick the smallest one that still looks sharp:
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A mountain lake at sunrise">
- Each entry in
srcsetlists a file and its real width (400w= 400 pixels wide). sizestells the browser how wide the image will display: full viewport width (100vw) below 600px, otherwise half (50vw).- The browser does the maths and downloads the best match for the device β even accounting for high-density "retina" screens.
The src is a fallback for very old browsers. Always keep a meaningful alt.
Step 3: <picture> for art direction
Sometimes you don't just want a smaller version β you want a different image. A wide banner looks great on desktop but the subject becomes tiny on a phone, where a tightly cropped square works better. The <picture> element swaps whole files based on a media condition:
<picture>
<source media="(min-width: 700px)" srcset="banner-wide.jpg">
<source media="(max-width: 699px)" srcset="banner-square.jpg">
<img src="banner-wide.jpg" alt="Our summer sale">
</picture>
The browser uses the first <source> whose media condition matches. The plain <img> at the end is the required fallback and also carries the alt text. This deliberate swapping is called art direction.
Step 4: media queries
A media query wraps CSS rules so they only apply when a condition is true β most often a screen-width range:
/* base styles: apply to ALL screens (mobile first) */
.cards { display: grid; grid-template-columns: 1fr; gap: 16px; }
/* tablets and up */
@media (min-width: 600px) {
.cards { grid-template-columns: 1fr 1fr; }
}
/* desktops and up */
@media (min-width: 900px) {
.cards { grid-template-columns: 1fr 1fr 1fr; }
}
This is mobile-first design. The default is a single column for phones. As the screen grows past each min-width breakpoint, more columns are added. Each query only adds to what came before, so the CSS stays clean.
A complete worked example
Save this as responsive.html and open it. Slowly drag the window from very narrow to very wide and watch the layout step from one column to two to three.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Responsive Demo</title>
<style>
* { box-sizing: border-box; }
body { font-family: sans-serif; margin: 0; padding: 16px; }
img { max-width: 100%; height: auto; border-radius: 8px; display: block; }
/* Mobile first: one column */
.cards { display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 600px) {
.cards { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 900px) {
.cards { grid-template-columns: 1fr 1fr 1fr; }
}
.card { background: #f1f5f9; padding: 16px; border-radius: 10px; }
/* The label updates to show the current breakpoint, just for the demo */
.label::after { content: "π± mobile: 1 column"; }
@media (min-width: 600px) { .label::after { content: "π» tablet: 2 columns"; } }
@media (min-width: 900px) { .label::after { content: "π₯οΈ desktop: 3 columns"; } }
</style>
</head>
<body>
<h1>Responsive Gallery</h1>
<p class="label"></p>
<div class="cards">
<div class="card">
<img src="https://picsum.photos/id/1015/600/400" alt="A river through a canyon">
<h2>Canyon</h2>
</div>
<div class="card">
<img src="https://picsum.photos/id/1016/600/400" alt="A mountain range at dusk">
<h2>Mountains</h2>
</div>
<div class="card">
<img src="https://picsum.photos/id/1018/600/400" alt="A forest from above">
<h2>Forest</h2>
</div>
</div>
</body>
</html>
Reading the result: every <img> is fluid thanks to max-width: 100%, so it always fits its card. The grid starts as one column and grows to two then three as the media-query breakpoints are crossed. The little .label text even tells you which breakpoint you're in. The <meta name="viewport"> tag in the head is essential β without it, phones pretend to be desktop-width and ignore your media queries.
Try it yourself
- Add a breakpoint. Insert a
@media (min-width: 1200px)query that makes.cardsfour columns wide. Stretch the window to test it. - Go fluid with one line. Replace all the media queries with
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));and see the same effect with no breakpoints β review CSS Grid Layout Basics for how that works. - Try art direction. Wrap one image in a
<picture>with two<source>tags pointing at differentpicsum.photoscrops (e.g. a600/400landscape and a400/400square) for differentmin-widthvalues.
Challenge: Build a responsive navbar that shows links in a row on wide screens but stacks them into a column below 600px. Use a media query to switch the container's flex-direction from row to column. Combine it with the responsive images here to make a complete, mobile-friendly page header.
Quick quiz
Test yourself and earn XP
What does max-width: 100% do to an image?
max-width: 100% lets the image shrink to fit its container but never exceed its own natural width.
What is the purpose of the srcset attribute?
srcset lists multiple image files; the browser chooses the most appropriate one for the device.
When does a media query's CSS apply?
A media query's rules apply only while its condition β such as min-width: 600px β is met.
What does mobile-first design mean?
Mobile-first means the default styles target small screens and you layer on enhancements with min-width queries.
Which element lets you swap entirely different images at breakpoints?
<picture> with <source> media conditions lets you serve different images for different screens (art direction).
FAQ
Use srcset (with sizes) when you want the SAME image at different resolutions and you trust the browser to pick the best one for the screen and pixel density. Use <picture> with <source media=...> when you want DIFFERENT images at different breakpoints β for example a wide landscape crop on desktop and a tight square crop on mobile. That deliberate swapping is called art direction.
Prefer min-width with a mobile-first approach: write your base CSS for the smallest screen, then add min-width queries to progressively enhance for larger screens. This keeps the simplest experience as the default and tends to produce cleaner, less repetitive CSS than starting big and overriding down with max-width.
Keep exploring
More in Coding