Responsive Images
Responsive images use the srcset and sizes attributes, plus the <picture> element, to let the browser pick the best image file for each device's screen size, resolution, and supported formats, saving bandwidth without sacrificing sharpness.
Part of the free HTML & CSS course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll serve every visitor an image sized for their screen, swap crops and modern formats with < picture > , lazy-load what's off-screen, and reserve space so your page never jumps while images load.
What You'll Learn
💡 Think of It Like This
Responsive images are like a coffee shop offering small, medium and large cups. You wouldn't pour a venti into someone who asked for an espresso — you'd waste coffee and they couldn't drink it all. A 4000-pixel photo sent to a phone is exactly that waste: megabytes the screen can't even show, paid for by the visitor's data plan and your load time.
With srcset you hand the browser the whole menu of sizes and let it order the cup that fits the cup-holder ( sizes ). The phone sips a 400px file; the 4K monitor gets the 2000px one. Everyone gets a full cup, nobody pays for spillage — and because you reserved the right-sized cup-holder up front ( aspect-ratio ), the table never wobbles while the drink arrives.
1. srcset & sizes — Resolution Switching
Images are usually the heaviest thing on a page — often 60–80% of its total weight. Resolution switching means shipping the same picture at several file sizes and letting the browser download whichever fits the screen. You list the files in srcset , each tagged with its real pixel width using the w descriptor ( 800w means "this file is 800 pixels wide").
On its own, srcset isn't enough — the browser chooses the file before it has laid out the page, so it can't yet know how wide the image will appear. That's the job of sizes : it tells the browser, for each viewport width, how wide the image's slot will be. The browser combines sizes with the device's pixel density to pick the smallest file that still looks sharp.
Run the worked example, then resize the preview. Read every comment — they state exactly what each attribute does.
2. The < picture > Element — Art Direction & Modern Formats
Where srcset serves the same image at different sizes, < picture > lets you serve different images entirely . This is called art direction : a wide landscape crop on desktop, but a tight square or portrait crop on mobile so the subject stays large enough to see. You wrap one or more < source > elements and a final fallback < img > inside it.
The browser reads the < source > elements top to bottom and uses the first one that matches . Each source can match on a media query (for art direction) or a type (for formats like image/avif and image/webp ). The trailing < img > is mandatory — it's both the fallback for old browsers and the element that actually carries the alt text.
3. loading="lazy" & decoding="async" — Faster Pages
loading="lazy" tells the browser to defer downloading an image until the user scrolls near it. On a long gallery that can turn dozens of downloads into just the two or three that are actually on screen, slashing your initial page weight. The catch: never lazy-load an image that's visible on first paint (your hero) — deferring it delays your Largest Contentful Paint and hurts Core Web Vitals.
decoding="async" is a smaller win that pairs nicely: it lets the browser decode the image off the main thread so a big picture can't briefly freeze scrolling or interaction. Use it on content images you're not racing to paint immediately.
4. width/height + aspect-ratio — Stop the Layout Shift
Ever seen text leap down the page as an image pops in? That's Cumulative Layout Shift (CLS) , a Core Web Vital you can score badly on. It happens because the browser doesn't know how tall an image will be until the file arrives, so it reserves no space and then shoves everything down once it loads.
Two fixes, used together. First, always put width and height attributes on every < img > — even when CSS makes it fluid with width: 100%; height: auto; . The browser reads them as a ratio and reserves the right box up front. Second, for CSS-driven boxes (or placeholders), set the aspect-ratio property so the container holds its shape before anything loads.
5. object-fit & object-position — Fit Any Box
When you force an image into a fixed box, its own shape rarely matches. By default it stretches and looks squashed. object-fit fixes that — it's like background-size , but for a real < img > . cover fills the box and crops the overflow (no distortion); contain fits the whole image inside and may leave gaps; fill stretches it (the ugly default).
When cover crops, it crops from the centre by default — which can chop off a head. object-position lets you steer which part survives the crop, e.g. object-position: top; to keep faces in frame.
🎯 Your Turn #1 — Wire up srcset + sizes
The image below only has a single src , so every device downloads the big file. Give the browser a menu of sizes to choose from. Fill in the blanks marked ___ , then run it and check the expected result in the comments.
🎯 Your Turn #2 — Art direction + a no-CLS box
Build a < picture > that swaps crop at 600px, give the fallback < img > its dimensions so the page doesn't jump, and crop it to a fixed shape without distortion. Fill in each ___ and verify against the comments.
🧩 Mini-Challenge — A performant card from scratch
Support is faded now — only an outline is given. Build one image card that uses everything from this lesson. Lean on the worked examples in sections 1–5 if you get stuck.
⚠️ Common Errors (and the fix)
- The page jumps as images load (CLS). You left width / height off your < img > , so the browser reserves no space until the file arrives. Fix: add width and height attributes (keep height: auto in CSS for fluidity), or set aspect-ratio on the container.
- srcset vs sizes confusion — wrong file gets picked. Using w descriptors in srcset without a sizes attribute makes the browser assume 100vw and often over-download. Fix: always pair w descriptors with a sizes value that describes the real slot width.
- Shipping one huge image to everyone. A single src="photo-2000.jpg" wastes megabytes on phones and tanks your load time. Fix: export the photo at a few widths and list them in srcset so each device downloads only what it needs.
- Missing the fallback < img > inside < picture > . A < picture > with only < source > elements shows nothing where none match. Fix: end every < picture > with a plain < img > — it carries the fallback and the alt .
- Lazy-loading the hero. loading="lazy" on an above-the-fold image delays Largest Contentful Paint and hurts Core Web Vitals. Fix: only lazy-load below-the-fold images; let the hero load eagerly.
- object-fit does nothing. object-fit only has an effect when the image has a fixed width and height to fill. Fix: give the < img > explicit dimensions (or a sized container) before applying cover / contain .
📋 Quick Reference
Goal
Use this
Serve sizes of the same image
srcset="a.jpg 400w, b.jpg 800w"
Describe the display slot
sizes="(max-width:600px) 100vw, 60vw"
Fixed-size image on retina
srcset="i.png 1x, i@2x.png 2x"
Different crop per viewport
< source media="(min-width:600px)" srcset="…" >
Modern format + fallback
< source type="image/avif" srcset="…" >
Defer off-screen download
loading="lazy" decoding="async"
Reserve space (no CLS)
width="800" height="450" + aspect-ratio: 16/9
Fill a box without distortion
object-fit: cover;
Steer the crop
object-position: top;
❓ Frequently Asked Questions
🎉 Lesson Complete
You can now ship images that are fast, sharp on every screen, and don't shift the layout. The essentials:
- ✅ srcset + sizes = resolution switching (same image, right-sized file)
- ✅ < picture > = art direction and AVIF/WebP with a JPEG fallback (first matching < source > wins)
- ✅ loading="lazy" + decoding="async" defer off-screen work — never on the hero
- ✅ width / height + aspect-ratio reserve space to kill layout shift (CLS)
- ✅ object-fit: cover + object-position fit any box without distortion
Next up: Transitions , where you'll animate state changes smoothly with CSS.
Practice quiz
What does the descriptor in srcset describe?
- The pixel density of the screen
- The weight in kilobytes
- The real pixel width of the image file
- The widget type
Answer: The real pixel width of the image file. The w descriptor states each file's real pixel width, e.g. image-800.jpg 800w means that file is 800px wide.
Why must you pair descriptors in srcset with a attribute?
- To tell the browser how wide the image slot will be so it can pick the right file
- To enable lazy loading
- To set the aspect ratio
- To add alt text
Answer: To tell the browser how wide the image slot will be so it can pick the right file. sizes tells the browser the slot width at each viewport; without it the browser assumes 100vw and often over-downloads.
When should you reach for the <picture> element instead of plain srcset?
- When only the resolution changes
- When the image is decorative
- When you need lazy loading
- When the actual image changes — a different crop (art direction) or format
Answer: When the actual image changes — a different crop (art direction) or format. Use <picture> for art direction (different crops) or format swaps (AVIF/WebP with a JPEG fallback); srcset is for same-image resolution switching.
What is the role of the trailing <img> inside a <picture> element?
- It is optional decoration
- It is the mandatory fallback and carries the alt text
- It sets the page background
- It must be empty
Answer: It is the mandatory fallback and carries the alt text. The <img> is mandatory: it is the fallback when no <source> matches and it carries the alt text.
How does the browser choose among the <source> elements in a <picture>?
- It uses the first <source> that matches, top to bottom
- It uses the last one
- It uses the smallest file
- It picks randomly
Answer: It uses the first <source> that matches, top to bottom. The browser reads sources top to bottom and uses the first one that matches its media query or supported type.
Which descriptor is best for a fixed-size logo on retina screens?
- The w descriptor
- The vw descriptor
- The x descriptor (e.g. 2x)
- The dpi descriptor
Answer: The x descriptor (e.g. 2x). The x descriptor (e.g. logo@2x.png 2x) describes pixel density and suits fixed-size images like icons and logos.
Which attribute defers an off-screen image's download until you scroll near it?
- decoding="async"
- loading="lazy"
- fetchpriority="low"
- defer
Answer: loading="lazy". loading="lazy" delays downloading until the image nears the viewport — ideal for below-the-fold images.
Should you set width and height attributes even when CSS makes the image fluid with width:100%?
- No, they are ignored
- Only on the hero image
- Only for SVGs
- Yes — they give the intrinsic ratio so the browser reserves space and avoids CLS
Answer: Yes — they give the intrinsic ratio so the browser reserves space and avoids CLS. Even with width:100%; height:auto, the attributes provide the intrinsic aspect ratio so the browser reserves the right box and prevents layout shift.
Which object-fit value fills the box and crops the overflow without distorting the image?
- fill
- cover
- contain
- scale-down
Answer: cover. object-fit: cover fills the box and crops overflow with no distortion; contain letterboxes and fill stretches.
When crops an image, which property steers which part stays visible?
- object-align
- background-position
- object-position
- crop-anchor
Answer: object-position. object-position (e.g. object-position: top) controls which part of the image survives the crop.
Continue this course
- Previous: Design Systems
- Next: Transitions