Responsive Images
1. Introduction
Images are typically the largest assets on a webpage and the single biggest contributor to slow load times. A 2000-pixel image served to a 400-pixel mobile screen wastes bandwidth, drains battery, and slows Core Web Vitals scores. HTML5 provides two powerful mechanisms โ the srcset attribute and the <picture> element โ that let the browser automatically select the most appropriate image based on screen size, resolution, and format support. This lesson teaches you to use both correctly.
2. Concept
Responsive Image Techniques
| Technique | Element / Attribute | Best For |
|---|---|---|
| Resolution switching | img srcset + sizes | Same image, different sizes for different screens |
| Art direction | <picture> + <source media> | Different crops/compositions at different breakpoints |
| Format switching | <picture> + <source type> | Serve modern formats (WebP, AVIF) with fallback |
| Lazy loading | img loading=”lazy” | Defer off-screen images until near viewport |
srcset tells the browser which image files are available. The sizes attribute tells it how wide the image will be displayed at each viewport width. The browser then picks the optimal file โ you cannot force which one it chooses, and that is intentional (it may also factor in cached images and network conditions).width and height attributes on <img> elements even with responsive images. The browser uses these to reserve space before the image loads, preventing Cumulative Layout Shift (CLS) โ one of Google’s Core Web Vitals metrics.loading="lazy" on above-the-fold images (the hero image, logo, or any image visible without scrolling). Lazy loading delays these critical images, hurting Largest Contentful Paint (LCP). Only apply it to images below the fold.3. Basic Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Responsive Images Demo</title>
</head>
<body>
<!-- Resolution switching: srcset + sizes -->
<img
src="/images/hero-800.jpg"
srcset="
/images/hero-400.jpg 400w,
/images/hero-800.jpg 800w,
/images/hero-1200.jpg 1200w,
/images/hero-1600.jpg 1600w
"
sizes="
(max-width: 600px) 100vw,
(max-width: 960px) 80vw,
1200px
"
alt="Mountain landscape at sunrise with mist in the valley"
width="1600" height="900"
>
<!-- Art direction: different crop at different viewport widths -->
<picture>
<source
media="(max-width: 600px)"
srcset="/images/product-portrait-400.jpg 1x, /images/product-portrait-800.jpg 2x"
>
<source
media="(max-width: 960px)"
srcset="/images/product-square-600.jpg 1x, /images/product-square-1200.jpg 2x"
>
<img
src="/images/product-landscape-1200.jpg"
alt="ProBook 15 laptop open on a desk showing the display"
width="1200" height="675"
loading="lazy"
>
</picture>
<!-- Format switching: AVIF โ WebP โ JPEG fallback -->
<picture>
<source type="image/avif" srcset="/images/hero.avif">
<source type="image/webp" srcset="/images/hero.webp">
<img
src="/images/hero.jpg"
alt="Aerial view of a coastal city at sunset"
width="1200" height="630"
loading="lazy"
>
</picture>
</body>
</html>
4. How It Works
Step 1 โ srcset with Width Descriptors
Each entry in srcset pairs a URL with a width descriptor (w). This tells the browser the intrinsic pixel width of each file. The browser then uses the sizes attribute to calculate which file to download based on the current viewport and device pixel ratio.
Step 2 โ sizes Attribute
sizes is a comma-separated list of media conditions and slot widths. The browser evaluates them top-to-bottom and uses the first matching condition’s slot width. The final entry (no media condition) is the default. This tells the browser how wide the image will actually be rendered โ essential for it to pick the right srcset entry.
Step 3 โ picture for Art Direction
<picture> wraps multiple <source> elements and a fallback <img>. The browser uses the first <source> whose media condition matches. This allows completely different image crops or compositions at different breakpoints โ not just different resolutions of the same image.
Step 4 โ Format Switching
<source type="image/avif"> serves AVIF (typically 50% smaller than JPEG) to browsers that support it. Unsupported browsers fall through to WebP, then to the JPEG fallback <img>. This gives modern browsers maximum compression with zero developer burden on old browsers.
5. Real-World Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TechStore โ ProBook 15</title>
<style>
.product-hero { width: 100%; max-width: 900px; display: block; }
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; margin-top: 1rem; }
.gallery img { width: 100%; height: auto; border-radius: 4px; }
</style>
</head>
<body>
<main>
<h1>ProBook 15 Laptop</h1>
<!-- Hero: AVIF/WebP + art direction + LCP image (no lazy) -->
<picture>
<source
media="(max-width: 480px)"
type="image/avif"
srcset="/products/probook15-mobile.avif"
>
<source
media="(max-width: 480px)"
type="image/webp"
srcset="/products/probook15-mobile.webp"
>
<source
type="image/avif"
srcset="/products/probook15-desktop.avif"
>
<source
type="image/webp"
srcset="/products/probook15-desktop.webp"
>
<img
class="product-hero"
src="/products/probook15-desktop.jpg"
alt="ProBook 15 laptop open at 90 degrees on a white desk, display showing a code editor"
width="900" height="600"
fetchpriority="high"
>
</picture>
<!-- Gallery: lazy loading, srcset for HiDPI screens -->
<div class="gallery">
<img
src="/products/probook15-side.jpg"
srcset="/products/probook15-side.jpg 1x, /products/probook15-side@2x.jpg 2x"
alt="ProBook 15 viewed from the side showing its thin profile"
width="400" height="300"
loading="lazy"
>
<img
src="/products/probook15-keyboard.jpg"
srcset="/products/probook15-keyboard.jpg 1x, /products/probook15-keyboard@2x.jpg 2x"
alt="Backlit keyboard of the ProBook 15 in a dark environment"
width="400" height="300"
loading="lazy"
>
<img
src="/products/probook15-ports.jpg"
srcset="/products/probook15-ports.jpg 1x, /products/probook15-ports@2x.jpg 2x"
alt="Left side showing 2x USB-A, 1x USB-C, HDMI, and SD card reader ports"
width="400" height="300"
loading="lazy"
>
</div>
</main>
</body>
</html>
6. Common Mistakes
❌ Serving one large image to all devices
<img src="/images/hero-3000.jpg" alt="Hero" width="3000" height="1600">
✓ Provide multiple sizes with srcset for efficient delivery
<img
src="/images/hero-800.jpg"
srcset="/images/hero-400.jpg 400w, /images/hero-800.jpg 800w, /images/hero-1600.jpg 1600w"
sizes="(max-width:600px) 100vw, 800px"
alt="Hero" width="1600" height="900"
>
❌ lazy loading the LCP image (hurts Largest Contentful Paint)
<img src="/hero.jpg" alt="Hero" loading="lazy">
✓ Use fetchpriority=”high” on LCP images instead
<img src="/hero.jpg" alt="Hero" fetchpriority="high">
7. Try It Yourself
8. Quick Reference
| Attribute / Element | Purpose | Notes |
|---|---|---|
| img srcset (w descriptors) | List of available image widths | Pair with sizes attribute |
| img sizes | Display width at each breakpoint | Browser uses to pick srcset entry |
| <picture> + <source media> | Art direction โ different images per breakpoint | First matching source wins |
| <source type> | Format switching (AVIF, WebP, JPEG) | Modern formats with graceful fallback |
| loading=”lazy” | Defer off-screen images | Never on above-fold / LCP images |
| fetchpriority=”high” | Prioritise LCP image download | Use on hero/above-fold image |
| width + height attributes | Reserve space before load | Prevents CLS โ always include |