Lesson 23 of 32
Responsive Images
srcset and sizes for letting the browser pick the right resolution, <picture> for art direction, object-fit/object-position for fixed-size containers, and the loading="lazy" attribute.
The earlier images lesson covered <img> and alt — the basics of putting a
picture on a page. This lesson is about a harder, more specific problem: the
same image needs to look right and load efficiently on everything from a
360px phone to a 4K monitor, and a single src can't do that well on its own.
The problem a single src has
<img src="photo.jpg" alt="A mountain lake at sunset" />If photo.jpg is large enough to look sharp on a wide desktop monitor, a
phone downloads that same large file and then shrinks it down with CSS —
wasting bandwidth on pixels it never shows. If instead photo.jpg is sized
for a phone, it looks soft and blurry stretched across a desktop screen.
One file, one resolution, can't be right for both.
srcset: offering the browser choices
srcset lists several versions of the same image, each labelled with its
actual pixel width using a width descriptor (400w means "this file is
400 pixels wide"):
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
alt="A mountain lake at sunset"
/>src stays as a fallback for browsers that don't understand srcset. Given
the srcset list, a modern browser picks whichever candidate makes sense for
the visitor's screen and pixel density — a phone might download photo-400.jpg,
a retina laptop might download photo-1200.jpg even though it's displaying
the image at a much smaller size on screen, because it has twice as many
physical pixels to fill per CSS pixel.
sizes: telling the browser how wide the image will be shown
Here's the part srcset alone can't answer: the browser knows each
candidate file's actual pixel width, but it doesn't automatically know how
wide the image is going to be displayed — that depends on your CSS layout,
which the browser hasn't fully worked out yet at the point it needs to start
downloading. sizes answers that question directly:
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(min-width: 900px) 600px, 100vw"
alt="A mountain lake at sunset"
/>Read sizes left to right, like a list of conditions checked in order: "if
the viewport is at least 900px wide, this image will be displayed at 600px
wide; otherwise, it will be displayed at 100% of the viewport width (100vw)."
With that information plus the srcset list, the browser can work out which
actual file is the smallest one still large enough to look sharp at the size
it'll be shown — and it makes that choice itself, per visitor, without you
writing any JavaScript or maintaining separate markup per device.
srcset and sizes only ever choose between different-resolution copies of
the same image. They cannot swap in a different picture. For that, you
need <picture>.
<picture>: true art direction
Sometimes the goal isn't "same photo, smaller file" — it's "a genuinely
different image or crop depending on screen size." A wide landscape shot
might work great on desktop but look pointless cropped down to a phone-width
sliver; a tighter, closer crop of the same subject might work much better on
a narrow screen. That's art direction, and it needs <picture>:
<picture>
<source media="(min-width: 900px)" srcset="lake-wide.jpg" />
<source media="(min-width: 500px)" srcset="lake-medium.jpg" />
<img src="lake-tight-crop.jpg" alt="A mountain lake at sunset" />
</picture>The browser checks each <source> in order and uses the first one whose
media condition matches the current viewport. If none match, it falls back
to the <img> at the bottom — which is also what non-supporting browsers and
screen readers use, and where alt always belongs (a <source> never takes
alt). This is the real, load-bearing distinction from srcset/sizes:
those pick a resolution of one image; <picture> picks between genuinely
different images.
srcset + sizes
Same image, different resolutions. The browser picks the most efficient file for the display size and pixel density.
<picture> + <source media>
Different images or crops entirely, chosen by breakpoint. Use this only when the content of the image itself should change.
object-fit and object-position: fitting an image into a fixed box
Once an image sits inside a container with a fixed width and height — a card
thumbnail, an avatar circle — its natural aspect ratio usually doesn't match
the container's, and the image distorts unless you tell it how to behave.
object-fit controls that:
.thumbnail {
width: 300px;
height: 200px;
object-fit: cover;
}coverscales the image to completely fill the box, cropping whatever overflows. No empty space, no distortion, but part of the image is cut off.containscales the image to fit entirely inside the box without cropping, which can leave empty space on two sides if the aspect ratios don't match.fill(the default) stretches the image to exactly fill the box, distorting it if the aspect ratios differ — usually not what you want.
object-position controls which part of the image stays visible when
cover has to crop something away — the default is centered, but you can
push the visible window toward an edge:
.portrait-thumbnail {
object-fit: cover;
object-position: top; /* keep the top of the image, e.g. a face, in frame */
}loading="lazy": deferring offscreen images
A long page with dozens of images — a product listing, a photo gallery — doesn't need every single one downloaded the instant the page loads. Most of them are below the fold and the visitor may never scroll to them at all.
<img src="photo.jpg" alt="A mountain lake at sunset" loading="lazy" />loading="lazy" is a standard, long-supported attribute that tells the
browser: don't fetch this image right away — wait until the visitor scrolls
close enough that it's about to come into view, then load it just in time.
It costs nothing to add and needs no JavaScript. The one case to skip it: an
image that's visible immediately when the page loads (like a hero image at
the top), where lazy-loading would only add a pointless delay before
something the visitor sees instantly anyway.
Try it yourself
The markup below uses object-fit to keep two differently-shaped source
images filling identical square frames without distortion, and shows the
srcset/sizes and <picture> syntax in comments since this embed can't
actually fetch external image files.
What to remember
srcsetwith width descriptors offers several resolutions of the same image;sizestells the browser how wide it'll actually display, so it can pick the right one.<picture>with<source media="...">is for true art direction — a genuinely different image or crop per breakpoint — whichsrcset/sizescannot do on their own.- Always keep a plain
<img>(withalt) as the fallback inside<picture>and as the base attribute alongsidesrcset. object-fit: coverfills a fixed-size box by cropping;containfits the whole image without cropping, possibly leaving gaps;object-positioncontrols which part stays visible when cropping happens.loading="lazy"defers offscreen images until the visitor scrolls near them — skip it on anything visible immediately on load.
Check yourself
3 questions · pass 3/3 to unlock Mobile-First Design
1.What problem does the
sizesattribute solve thatsrcsetalone doesn't?2.You need to show a tighter, cropped version of a photo on narrow screens and a wide version on large screens — not just a smaller file, an actually different crop. What's the right tool?
3.What does loading="lazy" on an <img> actually do?
3 left to answer