|$ curl https://forge-ai.dev/api/markdown?path=docs/html/picture
$cat docs/picture-element-&-responsive-images.md
updated Recently·20 min read·published

Picture Element & Responsive Images

HTMLImagesResponsivepicturesrcsetIntermediate🎯Free Tools
Introduction

Responsive images are one of the most impactful performance optimizations you can make for the modern web. Users visit your site on devices ranging from 4K desktop monitors to budget smartphones on cellular connections. Serving the same image to every device wastes bandwidth on small screens and delivers blurry, undersized images on large displays.

The <picture> element, combined with the srcset and sizes attributes on <img>, gives you three powerful capabilities:

Resolution Switching

Serve different image sizes based on the device viewport width and pixel density. A phone loads a 400px-wide image; a 4K monitor loads a 1600px-wide image — from the same HTML.

Art Direction

Serve entirely different image crops per breakpoint. A wide landscape hero on desktop becomes a tight, face-focused crop on mobile — the <picture> element swaps the entire image source.

Format Selection

Deliver modern formats like AVIF or WebP to browsers that support them, while falling back to JPEG or PNG for older browsers — all without JavaScript.

📝

note

The browser's image selection algorithm considers the viewport size, device pixel ratio, network conditions (with Save-Data header), and the sizes attribute to choose the most appropriate source. Understanding this algorithm is key to writing effective responsive image markup.
The srcset Attribute

The srcset attribute lets you declare multiple image candidates that the browser can choose from. Each candidate is described with either a width descriptor (w) or a pixel density descriptor (x).

Width Descriptors (w)

Width descriptors tell the browser the intrinsic width of each image candidate. The browser uses this information together with the sizes attribute to determine which source to load.

srcset-width.html
HTML
1<img
2 src="hero-800.jpg"
3 srcset="hero-400.jpg 400w,
4 hero-800.jpg 800w,
5 hero-1200.jpg 1200w,
6 hero-1600.jpg 1600w"
7 sizes="(max-width: 600px) 100vw,
8 (max-width: 1200px) 50vw,
9 800px"
10 alt="Mountain landscape at sunset"
11 loading="lazy"
12 width="1600"
13 height="900"
14/>

Pixel Density Descriptors (x)

Pixel density descriptors tell the browser the device pixel ratio each image is intended for. Use this when the image is always displayed at the same CSS size but you want to provide sharper versions for high-density screens.

srcset-density.html
HTML
1<img
2 src="logo.png"
3 srcset="logo.png 1x,
4 logo@2x.png 2x,
5 logo@3x.png 3x"
6 alt="Company logo"
7 width="200"
8 height="60"
9/>

How the Browser Chooses

When using width descriptors, the browser:

  1. Reads the sizes attribute to determine the image's layout width at the current viewport
  2. Multiplies the layout width by the device pixel ratio to get the needed pixel width
  3. Selects the smallest candidate that is greater than or equal to the needed width
  4. If no candidate is large enough, selects the largest available candidate

warning

Never mix width descriptors (w) and pixel density descriptors (x) in the same srcset attribute. The browser will ignore the entire srcset if you do. Use one or the other.

Complete example with multiple breakpoints for a typical layout:

srcset-breakpoints.html
HTML
1<!-- Full-width on mobile, 50% on tablet, 33% on desktop -->
2<img
3 src="product-600.jpg"
4 srcset="product-400.jpg 400w,
5 product-600.jpg 600w,
6 product-800.jpg 800w,
7 product-1200.jpg 1200w"
8 sizes="(max-width: 640px) 100vw,
9 (max-width: 1024px) 50vw,
10 33vw"
11 alt="Product photo"
12 width="1200"
13 height="800"
14 loading="lazy"
15 decoding="async"
16/>
preview
The sizes Attribute

The sizes attribute tells the browser how wide the image will be displayed in the layout — not the viewport size, but the actual rendered width of the image element. Without sizes, the browser assumes the image is 100vw (full viewport width).

Syntax

sizes-syntax.html
HTML
1sizes="(media-condition) length,
2 (media-condition) length,
3 fallback-length"

Layout Scenarios

sizes-examples.html
HTML
1<!-- Full-width hero image -->
2<img
3 srcset="hero-800.jpg 800w, hero-1600.jpg 1600w"
4 sizes="100vw"
5 src="hero-800.jpg"
6 alt="Hero banner"
7/>
8
9<!-- Sidebar image: always 300px wide -->
10<img
11 srcset="avatar-300.jpg 300w, avatar-600.jpg 600w"
12 sizes="300px"
13 src="avatar-300.jpg"
14 alt="Author avatar"
15/>
16
17<!-- Grid thumbnail: varies by viewport -->
18<img
19 srcset="thumb-300.jpg 300w, thumb-600.jpg 600w"
20 sizes="(max-width: 768px) 50vw,
21 (max-width: 1200px) 33vw,
22 25vw"
23 src="thumb-300.jpg"
24 alt="Gallery thumbnail"
25/>

The Math Behind Source Selection

The browser performs this calculation for each candidate in srcset:

1. Determine layout width from sizes:

e.g. (max-width: 768px) 50vw on a 1000px viewport = 500px

2. Multiply by device pixel ratio:

500px x 2 (Retina) = 1000px needed

3. Select smallest candidate >= needed:

srcset: 400w (too small), 800w (too small), 1200w ✓

info

Always write sizes to describe the image element's layout width, not the viewport. If your image is in a sidebar that is always 300px, write sizes="300px" — not sizes="100vw". The browser trusts your sizes value and it directly affects which source is loaded.
The Picture Element

The <picture> element wraps zero or more <source> elements and one <img> element. It provides art direction and format selection — two things that srcset alone cannot do.

Format Selection with type

The type attribute on <source> lets the browser skip sources with unsupported MIME types. This avoids downloading a file only to discover it is not supported.

picture-format.html
HTML
1<picture>
2 <source srcset="photo.avif" type="image/avif" />
3 <source srcset="photo.webp" type="image/webp" />
4 <img
5 src="photo.jpg"
6 alt="Beach sunset photo"
7 width="1200"
8 height="800"
9 loading="lazy"
10 />
11</picture>

Art Direction with media

Use the media attribute on <source> to swap entirely different images at different breakpoints. The browser uses the first <source> whose media condition matches.

picture-art-direction.html
HTML
1<picture>
2 <!-- Desktop: wide landscape crop -->
3 <source
4 media="(min-width: 1024px)"
5 srcset="hero-landscape.avif"
6 type="image/avif"
7 />
8 <!-- Tablet: medium crop with context -->
9 <source
10 media="(min-width: 640px)"
11 srcset="hero-tablet.avif"
12 type="image/avif"
13 />
14 <!-- Mobile: tight portrait crop -->
15 <source
16 srcset="hero-mobile.avif"
17 type="image/avif"
18 />
19 <!-- Fallback -->
20 <img
21 src="hero-landscape.jpg"
22 alt="Mountain landscape"
23 width="1200"
24 height="600"
25 />
26</picture>
preview

info

The <img> element inside <picture> is mandatory. It serves as the fallback for browsers that do not support <picture> and as the actual image element that holds the displayed image. Never omit it.

When to Use picture vs srcset Alone

ScenarioUseWhy
Same image, different sizessrcset + sizesBrowser handles resolution switching natively
Different crops per breakpoint<picture> + mediaArt direction requires full source swap
Format negotiation (AVIF/WebP)<picture> + typetype attribute lets browser skip unsupported formats
Both art direction + format<picture> + media + typeCombine both in one element
Art Direction in Practice

Art direction means showing a different visual composition at different screen sizes. A landscape photo with a subject centered may look great on desktop, but on a 375px-wide phone, the subject becomes tiny and lost. Art direction solves this by using different image crops for each context.

Three-Breakpoint Example

Consider a hero image of a person speaking at a conference. Here is how art direction adapts the crop:

art-direction.html
HTML
1<picture>
2 <!-- Desktop: full wide landscape — speaker + stage + crowd -->
3 <source
4 media="(min-width: 1024px)"
5 srcset="hero-desktop.avif 1200w"
6 type="image/avif"
7 />
8
9 <!-- Tablet: medium crop — speaker + stage, tighter -->
10 <source
11 media="(min-width: 640px)"
12 srcset="hero-tablet.avif 800w"
13 type="image/avif"
14 />
15
16 <!-- Mobile: tight portrait crop — just the speaker's face -->
17 <source srcset="hero-mobile.avif 400w" type="image/avif" />
18
19 <!-- Fallback: JPEG versions for browsers without AVIF -->
20 <source
21 media="(min-width: 1024px)"
22 srcset="hero-desktop.jpg 1200w"
23 />
24 <source
25 media="(min-width: 640px)"
26 srcset="hero-tablet.jpg 800w"
27 />
28 <img
29 src="hero-mobile.jpg"
30 alt="Keynote speaker presenting at the annual developer conference"
31 width="1200"
32 height="600"
33 loading="eager"
34 fetchpriority="high"
35 />
36</picture>
preview

best practice

Art direction images should be separate files with intentional crops, not just scaled versions of the same file. Use object-fit: cover in CSS as a partial solution, but true art direction requires different source photographs that emphasize the subject appropriately at each size.
Resolution Switching Deep Dive

Device pixel ratio (DPR) determines how many physical pixels map to one CSS pixel. A standard display has a DPR of 1, Apple Retina displays have a DPR of 2, and some high-end phones have a DPR of 3. Higher DPR means more physical pixels per CSS pixel — and sharper images if you serve higher-resolution sources.

DPRPhysical Pixels per CSS PixelNeeded Source Width for 400px ElementBandwidth vs 1x
1x1400px1.0x
2x4 (2x2)800px4.0x (pixels), ~2x (file size)
3x9 (3x3)1200px9.0x (pixels), ~3x (file size)

Serving a 2x image to a 1x display wastes approximately 75% of the pixel data. Resolution switching solves this by letting the browser select the appropriate source for each DPR.

The Formula

To size your srcset candidates correctly:

1. Identify your largest layout width: maxLayoutWidth

2. Account for the highest DPR you want to support: maxDPR (usually 2 or 3)

3. Largest source in srcset should be: maxLayoutWidth x maxDPR

4. Include sources for each breakpoint at each relevant DPR

resolution-formula.html
HTML
1<!-- Image displayed at 100vw (full width)
2 Max viewport: 1440px
3 DPR: up to 3x
4 Largest source needed: 1440 * 3 = 4320w
5
6 But 3x on 1440px is extreme — cap at 2x:
7 Largest source: 1440 * 2 = 2880w
8-->
9<img
10 src="photo-800.jpg"
11 srcset="photo-400.jpg 400w,
12 photo-600.jpg 600w,
13 photo-800.jpg 800w,
14 photo-1200.jpg 1200w,
15 photo-1600.jpg 1600w,
16 photo-2400.jpg 2400w"
17 sizes="100vw"
18 alt="Landscape photo"
19 width="1440"
20 height="900"
21 loading="lazy"
22/>
🔥

pro tip

In practice, capping at 2x (not 3x) saves significant bandwidth with minimal visual difference. Most users cannot distinguish 2x from 3x on phone-sized screens. Only include 3x sources for hero images or above-the-fold content where maximum sharpness matters.
Format Selection (AVIF / WebP)

Image format selection is one of the easiest wins for performance. Modern formats like AVIF and WebP offer dramatically better compression than JPEG, often producing 30-50% smaller files at the same visual quality. The <picture> element lets you serve these formats transparently.

FormatCompressionTransparencyBrowser SupportBest For
AVIFBest (50%+ smaller than JPEG)YesChrome, Firefox, Safari 16.4+Photos, hero images
WebPGood (25-35% smaller than JPEG)Yes97%+ global supportAll image types
JPEGBaselineNo100% — universalFallback, legacy
PNGLarge (lossless)Yes100% — universalLogos, icons, transparency

Complete Format Negotiation Chain

format-chain.html
HTML
1<picture>
2 <!-- AVIF: best compression, newer support -->
3 <source
4 srcset="photo.avif"
5 type="image/avif"
6 />
7 <!-- WebP: good compression, wide support -->
8 <source
9 srcset="photo.webp"
10 type="image/webp"
11 />
12 <!-- JPEG: universal fallback -->
13 <img
14 src="photo.jpg"
15 alt="Descriptive alt text"
16 width="800"
17 height="600"
18 loading="lazy"
19 decoding="async"
20 />
21</picture>

Combining Format + Resolution Switching

format-resolution.html
HTML
1<picture>
2 <!-- AVIF with resolution switching -->
3 <source
4 type="image/avif"
5 srcset="hero-400.avif 400w,
6 hero-800.avif 800w,
7 hero-1200.avif 1200w,
8 hero-1600.avif 1600w"
9 sizes="(max-width: 768px) 100vw, 50vw"
10 />
11 <!-- WebP with resolution switching -->
12 <source
13 type="image/webp"
14 srcset="hero-400.webp 400w,
15 hero-800.webp 800w,
16 hero-1200.webp 1200w,
17 hero-1600.webp 1600w"
18 sizes="(max-width: 768px) 100vw, 50vw"
19 />
20 <!-- JPEG fallback -->
21 <img
22 src="hero-800.jpg"
23 srcset="hero-400.jpg 400w,
24 hero-800.jpg 800w,
25 hero-1200.jpg 1200w"
26 sizes="(max-width: 768px) 100vw, 50vw"
27 alt="Hero image with format and resolution switching"
28 width="1600"
29 height="900"
30 />
31</picture>

info

Use server-side content negotiation when possible. Your CDN or server can inspect the Accept header and serve AVIF, WebP, or JPEG automatically — eliminating the need for <picture> format fallbacks in your HTML. Cloudflare, Imgix, and Cloudinary all support this.
Lazy Loading

Lazy loading defers the download of off-screen images until the user scrolls near them. This dramatically reduces initial page load time and data usage, especially on image-heavy pages.

Native Lazy Loading

lazy-loading.html
HTML
1<!-- Lazy load below-the-fold images -->
2<img
3 src="photo.jpg"
4 alt="Below the fold photo"
5 loading="lazy"
6 decoding="async"
7 width="800"
8 height="600"
9/>
10
11<!-- Eager load above-the-fold images (default behavior) -->
12<img
13 src="hero.jpg"
14 alt="Above the fold hero"
15 loading="eager"
16 fetchpriority="high"
17 width="1600"
18 height="900"
19/>

Preloading Critical Images

For above-the-fold hero images, use fetchpriority="high" to tell the browser to prioritize this download. Combined with loading="eager", this ensures the most important image loads first.

preload-critical.html
HTML
1<!-- Hero image: highest priority -->
2<link rel="preload" as="image" href="hero.avif" type="image/avif" />
3<img
4 src="hero.jpg"
5 srcset="hero-800.jpg 800w, hero-1200.jpg 1200w"
6 sizes="100vw"
7 alt="Hero banner"
8 fetchpriority="high"
9 width="1200"
10 height="600"
11/>
12
13<!-- Below-the-fold: lazy load -->
14<img
15 src="content.jpg"
16 loading="lazy"
17 decoding="async"
18 alt="Content image"
19 width="800"
20 height="600"
21/>

Custom Lazy Loading with Intersection Observer

For more control over when images load (e.g., loading 200px before they enter the viewport), use the Intersection Observer API:

intersection-observer.js
JavaScript
1const observer = new IntersectionObserver(
2 (entries) => {
3 entries.forEach((entry) => {
4 if (entry.isIntersecting) {
5 const img = entry.target;
6 img.src = img.dataset.src;
7 img.srcset = img.dataset.srcset || "";
8 observer.unobserve(img);
9 }
10 });
11 },
12 { rootMargin: "200px 0px" } // Load 200px before visible
13);
14
15document.querySelectorAll("img[data-src]").forEach((img) => {
16 observer.observe(img);
17});
lazy-usage.html
HTML
1<!-- Usage with Intersection Observer -->
2<img
3 data-src="photo.jpg"
4 data-srcset="photo-400.jpg 400w, photo-800.jpg 800w"
5 alt="Lazy loaded with JS fallback"
6 width="800"
7 height="600"
8 class="lazy-placeholder"
9/>

warning

Never lazy load above-the-fold images. The hero image, logo, and any visible content images should use loading="eager" (or omit the attribute, since eager is the default). Lazy loading these images delays the largest contentful paint (LCP) and hurts Core Web Vitals.
Image Sizing Strategies

Proper image sizing prevents layout shift (CLS), ensures correct aspect ratios, and provides a smooth user experience. The key is combining intrinsic attributes (width/height on the element) with CSS sizing techniques.

Preventing Layout Shift

sizing-cls.html
HTML
1<!-- Always include width and height — CSS can override the display size -->
2<img
3 src="photo.jpg"
4 alt="Photo with dimensions"
5 width="800"
6 height="600"
7 loading="lazy"
8/>
9
10<!-- CSS controls the rendered size, HTML prevents CLS -->
11<style>
12 img {
13 width: 100%;
14 height: auto; /* Maintains aspect ratio from width/height */
15 }
16</style>

CSS aspect-ratio

aspect-ratio.html
HTML
1<!-- Modern approach: CSS aspect-ratio -->
2<style>
3 .responsive-img {
4 width: 100%;
5 height: auto;
6 aspect-ratio: 800 / 600; /* width / height */
7 object-fit: cover;
8 }
9</style>
10
11<img
12 src="photo.jpg"
13 alt="Photo with CSS aspect ratio"
14 class="responsive-img"
15 width="800"
16 height="600"
17/>

object-fit and object-position

object-fit.html
HTML
1<style>
2 /* Cover: fills container, may crop */
3 .img-cover {
4 width: 100%;
5 height: 300px;
6 object-fit: cover;
7 object-position: center 30%; /* Focus on top 30% */
8 }
9
10 /* Contain: fits inside container, may letterbox */
11 .img-contain {
12 width: 100%;
13 height: 300px;
14 object-fit: contain;
15 background: #0d0d0d;
16 }
17</style>
18
19<img src="photo.jpg" alt="Cover example" class="img-cover" />
20<img src="photo.jpg" alt="Contain example" class="img-contain" />
preview

best practice

Always set width and height attributes on <img> even when using responsive CSS. Modern browsers use these attributes to calculate the aspect ratio before the image loads, preventing layout shift. The CSS aspect-ratio property provides a second layer of protection.
Performance Checklist

Use this checklist to audit your responsive image implementation. Each item directly impacts Core Web Vitals and user experience.

CheckImpactTool
srcset + sizes on all content imagesBandwidth savings up to 70%Manual audit
Modern format (AVIF/WebP) with fallback30-50% smaller filesNetwork tab
loading="lazy" on below-the-foldFaster initial loadLighthouse
fetchpriority="high" on hero imageBetter LCP scoreLighthouse, WebPageTest
width + height attributes on all imgZero CLS from imagesLighthouse CLS audit
descriptive alt textAccessibility complianceaxe DevTools, Lighthouse
decoding="async" on lazy imagesNon-blocking decodePerformance audit
No oversized images (2x max)Bandwidth optimizationPageSpeed Insights

Recommended tools for auditing responsive images:

>
Lighthouse — Built into Chrome DevTools. Audits image sizing, lazy loading, and format.
>
PageSpeed Insights — Real-world data from Chrome UX Report plus lab analysis.
>
WebPageTest — Detailed waterfall showing image download timing, format, and size.
Common Mistakes

These are the most frequent responsive image mistakes and how to fix them.

MistakeProblemFix
Missing sizes attributeBrowser assumes 100vw, loads oversized imagesAlways add sizes matching your layout
Using <picture> when srcset sufficesUnnecessary complexity, duplicate source listsUse srcset for same-crop resolution switching
Missing <img> fallback in <picture>No image displays; accessibility brokenAlways include <img> as last child of <picture>
Same image for all devicesMobile downloads desktop-sized fileAdd srcset with multiple sizes + sizes attr
No width/height attributesCumulative Layout Shift (CLS)Add intrinsic dimensions for aspect ratio
Lazy loading hero imagesDelayed LCP, poor Core Web VitalsUse loading="eager" + fetchpriority="high"
Mixing w and x in srcsetEntire srcset is ignored by browserUse only w or only x per srcset
Missing alt textAccessibility violation, poor SEOAlways add descriptive alt attribute
before-after.html
HTML
1<!-- ❌ BAD: No sizes, no srcset, no lazy loading -->
2<img src="huge-desktop-photo.jpg" alt="Photo" />
3
4<!-- ✅ GOOD: Full responsive image setup -->
5<img
6 src="photo-600.jpg"
7 srcset="photo-400.jpg 400w,
8 photo-600.jpg 600w,
9 photo-800.jpg 800w,
10 photo-1200.jpg 1200w"
11 sizes="(max-width: 640px) 100vw,
12 (max-width: 1024px) 50vw,
13 33vw"
14 alt="Descriptive alt text"
15 width="1200"
16 height="800"
17 loading="lazy"
18 decoding="async"
19/>
Complete Real-World Example

This example combines every technique: art direction, format selection, resolution switching, lazy loading, and CLS prevention.

real-world-example.html
HTML
1<!-- Hero: art direction + format selection + eager load -->
2<picture>
3 <source
4 media="(min-width: 1024px)"
5 type="image/avif"
6 srcset="hero-desktop-1200.avif 1200w,
7 hero-desktop-1600.avif 1600w"
8 sizes="100vw"
9 />
10 <source
11 media="(min-width: 1024px)"
12 type="image/webp"
13 srcset="hero-desktop-1200.webp 1200w,
14 hero-desktop-1600.webp 1600w"
15 sizes="100vw"
16 />
17 <source
18 media="(min-width: 1024px)"
19 srcset="hero-desktop-1200.jpg 1200w,
20 hero-desktop-1600.jpg 1600w"
21 sizes="100vw"
22 />
23 <source
24 media="(min-width: 640px)"
25 type="image/avif"
26 srcset="hero-tablet-800.avif 800w,
27 hero-tablet-1200.avif 1200w"
28 sizes="100vw"
29 />
30 <img
31 src="hero-mobile-800.jpg"
32 srcset="hero-mobile-400.jpg 400w,
33 hero-mobile-800.jpg 800w"
34 sizes="100vw"
35 alt="Developer conference keynote"
36 width="1600"
37 height="600"
38 loading="eager"
39 fetchpriority="high"
40 />
41</picture>
42
43<!-- Content images: format selection + resolution switching + lazy load -->
44<picture>
45 <source
46 type="image/avif"
47 srcset="content-400.avif 400w,
48 content-800.avif 800w,
49 content-1200.avif 1200w"
50 sizes="(max-width: 768px) 100vw, 50vw"
51 />
52 <source
53 type="image/webp"
54 srcset="content-400.webp 400w,
55 content-800.webp 800w,
56 content-1200.webp 1200w"
57 sizes="(max-width: 768px) 100vw, 50vw"
58 />
59 <img
60 src="content-800.jpg"
61 srcset="content-400.jpg 400w,
62 content-800.jpg 800w,
63 content-1200.jpg 1200w"
64 sizes="(max-width: 768px) 100vw, 50vw"
65 alt="Content section image"
66 width="1200"
67 height="800"
68 loading="lazy"
69 decoding="async"
70 />
71</picture>
preview
$Blueprint — Engineering Documentation·Section ID: HTML-PICTURE·Revision: 1.0

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.