Serve the right image size and format with srcset, sizes, picture and lazy loading.
Why this matters
Images are usually the heaviest part of a page. Sending a 2400 px wide photo to a 360 px phone wastes data and slows the page, which hits people on prepaid plans and slow connections hardest. Images without dimensions also make the page jump as they load (layout shift), which is disorienting for everyone and especially for people with cognitive or vision impairments who lose their place. HTML has built-in tools for all of this.
Step 1: let images shrink
img { max-width: 100%; height: auto; }
max-width: 100% stops an image overflowing its container; height: auto keeps the aspect ratio. Put this in every stylesheet.
Step 2: always set width and height
<img src="trail-800.jpg" width="1600" height="900" alt="Hikers on a ridge at sunrise">
The numbers are the image's intrinsic size (or any pair with the same ratio). Browsers use them to reserve the right space before the file arrives, and the CSS above still makes it responsive. This is the single biggest fix for Cumulative Layout Shift. CSS aspect-ratio does the same job for elements without intrinsic dimensions.
Step 3: srcset and sizes (resolution switching)
When the same picture is needed at different sizes, offer several files and let the browser choose:
<img
src="trail-800.jpg"
srcset="trail-480.jpg 480w, trail-800.jpg 800w, trail-1600.jpg 1600w"
sizes="(min-width: 60rem) 50vw, 100vw"
width="1600" height="900"
alt="Hikers on a ridge at sunrise">
srcsetlists files with their real widths (480wmeans 480 pixels wide).sizestells the browser how wide the image will be displayed: half the viewport on wide screens, the full viewport otherwise. The browser needs this before CSS loads.- The browser multiplies the display width by the screen's pixel density and picks the smallest file that is big enough. A phone at 360 px and 3x density needs about 1080 px, so it takes
trail-1600.jpg; a 1x laptop showing it at 600 px takestrail-800.jpg. srcis the fallback for very old browsers.
For a fixed-size image (like an avatar) use density descriptors instead: srcset="avatar.jpg 1x, avatar@2x.jpg 2x".
Step 4: picture for formats and art direction
<picture> lets you offer modern formats with a fallback, or a different crop at different sizes:
<picture>
<source type="image/avif" srcset="team.avif">
<source type="image/webp" srcset="team.webp">
<img src="team.jpg" width="1200" height="800" alt="The support team at their desks">
</picture>
The browser uses the first <source> it supports. The <img> inside is required: it is what actually renders, and it carries alt, width, height and loading. For art direction, add media to a source, for example a tight square crop for phones:
<picture>
<source media="(min-width: 40rem)" srcset="banner-wide.jpg">
<img src="banner-square.jpg" width="800" height="800" alt="A bowl of ramen">
</picture>
Step 5: lazy-load what is off screen
<img src="gallery-3.jpg" loading="lazy" width="800" height="600" alt="...">
loading="lazy" delays images below the fold until the user scrolls near them. Do not lazy-load the main image at the top of the page (often the Largest Contentful Paint element); it should load immediately, and fetchpriority="high" can help it start sooner.
Alt text still matters
Every responsive technique above is about the file. The alt attribute is still what screen reader users get, and it goes on the <img>, not on <source>. Describe what the image conveys in context; use alt="" for decorative images. A chart or diagram needs a text equivalent nearby (a sentence or a data table) when alt is not enough.
Worked example: a recipe page
The page has a hero photo, a step-by-step gallery of six photos further down and a decorative divider. A good result:
- Hero:
srcsetwith 3 widths,sizes="100vw",width/height, noloading="lazy", a descriptivealt. - Gallery:
loading="lazy",width/height, alt text for each step ("Folding the dough in half"). - A
<picture>for the hero offering AVIF and WebP before the JPEG. - Divider:
alt=""or, better, a CSS border.
Common mistakes
srcsetwithwdescriptors but nosizes: the browser assumes100vwand may download a file far too big.- Missing
widthandheight, causing layout shift. - Lazy-loading the hero image.
- Putting
alton<picture>or<source>(it belongs on<img>). - Using CSS background images for content images, which have no alt text.
Quick recap
img { max-width: 100%; height: auto; }pluswidth/heightattributes.srcset+sizesfor resolution switching;<picture>for formats and art direction.loading="lazy"below the fold only.alton the<img>, always.- Reference: MDN: Responsive images and W3C WAI Images tutorial.
Try it
Open the Do tab and make a gallery page's images responsive. (The preview has no network, so images show as broken; the checks look at your markup and CSS.)