TechEarl

Responsive Images: srcset, sizes, picture, and Proportions

Choose the right image resource with srcset, sizes and picture, preserve proportions with CSS, reserve layout space and prioritize the hero image.

Ishan Karunaratne⏱️ 7 min readUpdated
Share thisCopied
Ship responsive images with native HTML: srcset width descriptors plus sizes for resolution switching, picture with media for art direction, and source type for AVIF and WebP fallback.
html
<img
  src="photo-800.jpg"
  srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
  sizes="(min-width: 760px) 720px, calc(100vw - 32px)"
  width="1600"
  height="900"
  alt="A terrier sitting beside a garden path"
/>
css
img {
  max-width: 100%;
  height: auto;
}

The HTML lets the browser choose an appropriate resource. The CSS lets the image fit its container without changing its proportions. The width and height attributes reserve the intended aspect ratio while the file loads. I check all three together when an image is blurry, too large or visibly squashed.

The sizes value above assumes a content column capped at 720 CSS pixels with 16-pixel side gutters on smaller screens. Change it to match your layout; it does not set that layout for you.

Resolution switching: srcset and sizes

Use width descriptors for different resolutions of the same image:

html
<img
  src="photo-800.jpg"
  srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w, photo-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw, 50vw"
  width="1600" height="900"
  alt="A terrier beside a garden path"
/>

Each w value must describe the actual pixel width of that resource. sizes describes its expected displayed width in CSS pixels. A 375 CSS-pixel slot on a 2x display suggests a resource around 750 pixels wide, but the browser makes the final choice using its own selection policy, cache and conditions. Do not rely on it always choosing one exact candidate.

For a fixed-size avatar, density descriptors are simpler:

html
<img src="avatar-48.png"
     srcset="avatar-48.png 1x, avatar-96.png 2x, avatar-144.png 3x"
     width="48" height="48" alt="Author portrait" />

Do not mix width and density descriptors in the same srcset.

The sizes gotcha

For a normal width-descriptor image without an applicable sizes value, the browser uses the default full-viewport slot. That can cause a narrow content-column image to download a larger resource than necessary.

Match the column cap, breakpoints and gutters:

html
<img src="photo-720.jpg"
     srcset="photo-480.jpg 480w, photo-720.jpg 720w, photo-1440.jpg 1440w"
     sizes="(min-width: 760px) 720px, calc(100vw - 32px)"
     width="1440" height="810" alt="A terrier beside a garden path" />

The first matching media condition wins. The final unqualified length is the fallback. sizes is a resource-selection hint, so still use CSS to set the actual layout.

Check the rendered width and currentSrc in browser developer tools. Test on a fresh page load at narrow and wide widths, including a high-density display. A browser may retain an already-loaded larger candidate when you resize; that is not proof your smaller candidate is broken.

Why images look squished

A rule such as width: 100%; height: 200px forces a box whose ratio changes with the container. The default object-fit: fill stretches the image into that box.

For a natural, uncropped image:

css
.article-image {
  max-width: 100%;
  height: auto;
  display: block;
}

max-width caps the width at the container; with an otherwise automatic width, a smaller image normally keeps its intrinsic size. An explicit larger width elsewhere can still enlarge it, so inspect the winning CSS rules. To intentionally fill the available width, use width: 100%; height: auto and provide enough source pixels for that display size.

Keep the width and height HTML attributes

html
<img src="photo.jpg" width="1600" height="900" alt="A terrier beside a garden path" />

These dimensions provide an initial ratio before the image downloads. CSS can resize the displayed image while keeping that ratio. Removing the attributes without reserving space another way can produce layout shift when the image arrives.

Use dimensions that agree with the intended source ratio. Correct width/height attributes are a straightforward default, but equivalent space reservation through CSS is also possible; the attributes are not a syntactic requirement for every valid image.

Reserve a ratio with CSS

When the markup cannot carry the dimensions, CSS can provide a fallback ratio:

css
.article-image {
  width: 100%;
  height: auto;
  aspect-ratio: auto 16 / 9;
}

For an image, auto 16 / 9 uses the supplied ratio before the intrinsic ratio is available, then uses the image's natural ratio. If those ratios differ, the box can still change after loading. Use the correct known ratio when you need stable space.

A plain aspect-ratio: 16 / 9 deliberately keeps that preferred ratio; it is not a universal repair for photos with different proportions. For a fixed crop, combine a chosen box with object-fit.

Fit an image into a fixed box

css
.thumbnail {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
}

cover fills the box and crops excess content. contain keeps the complete image visible and can leave empty space. fill stretches it. object-position chooses which part of a cropped image stays visible:

css
.portrait {
  width: 320px;
  height: 180px;
  object-fit: cover;
  object-position: center top;
}

For an image directly inside a flex container, cross-axis stretching can be another cause of distortion. Use a block wrapper or align-self: start when stretching is unwanted. Check the parent layout as well as the image's own width and height.

Art direction: picture with media

When mobile needs a different crop, select it with <picture>:

html
<picture>
  <source media="(min-width: 800px)"
          srcset="hero-wide.jpg" width="1600" height="900" />
  <source media="(min-width: 500px)"
          srcset="hero-medium.jpg" width="900" height="600" />
  <img src="hero-square.jpg" width="600" height="600"
       alt="A terrier standing beside a garden path" />
</picture>

The browser considers sources in order and chooses an applicable supported source. The <img> provides the rendered element, fallback and text alternative. Matching dimensions on art-directed sources let supporting browsers reserve the selected crop's ratio instead of assuming the square fallback ratio everywhere.

A <source> can also have its own width-descriptor srcset and sizes when each crop needs several resolutions. Keep the alternative text accurate for every selected crop.

Format fallback: picture with type

html
<picture>
  <source type="image/avif" srcset="photo.avif" />
  <source type="image/webp" srcset="photo.webp" />
  <img src="photo.jpg" width="1200" height="800"
       alt="A terrier beside a garden path" />
</picture>

The first supported source type wins. Compare actual image quality and transfer sizes when deciding the order; one format is not always smaller for every picture and encoder setting. Keep the fallback and every alternative available and correctly encoded.

Format and resolution selection can be combined:

html
<picture>
  <source type="image/avif"
          srcset="photo-400.avif 400w, photo-800.avif 800w, photo-1600.avif 1600w"
          sizes="(min-width: 760px) 720px, calc(100vw - 32px)" />
  <source type="image/webp"
          srcset="photo-400.webp 400w, photo-800.webp 800w, photo-1600.webp 1600w"
          sizes="(min-width: 760px) 720px, calc(100vw - 32px)" />
  <img src="photo-800.jpg" width="1600" height="900"
       alt="A terrier beside a garden path" />
</picture>

Lazy loading, decoding and fetch priority

For an image well below the initial viewport:

html
<img src="photo.jpg" width="1200" height="800"
     loading="lazy" decoding="async"
     alt="A terrier beside a garden path" />

For the likely Largest Contentful Paint image near the top of the page:

html
<img src="hero.jpg" width="1600" height="900"
     fetchpriority="high"
     alt="A terrier in the garden" />

Avoid lazy-loading the LCP image. fetchpriority is a hint about relative priority, not a command to bypass the browser's scheduler. Apply high priority selectively and measure the result; flagging every image high removes the distinction you wanted.

decoding="async" hints that the next paint need not wait for the image to decode. It does not guarantee which thread performs decoding. For most static images, dimensions, resource size, discoverability and loading priority matter more than this one hint.

Client Hints and the native HTML default

Older tutorials may use the legacy DPR, Width and Viewport-Width request headers. Those legacy device hints are deprecated. Newer Sec-CH- hints have their own support and server-negotiation requirements; deprecation of the older names does not mean every Client Hints feature is dead.

For an ordinary responsive image, srcset, sizes and picture provide a portable browser-controlled path without requiring a cooperating image server. A CDN-specific hint workflow still needs a fallback and careful cache configuration. Check the current documentation for the exact header instead of assuming universal browser support.

Quick decision guide

NeedUse
Same image, fluid display widthsrcset with width descriptors and accurate sizes
Fixed CSS size, different pixel densitiesDensity descriptors such as 1x and 2x
Different crop at a breakpointpicture and source media
Alternate file formatssource type with an image fallback
Natural, uncropped proportionsResponsive width and height: auto
Uniform cropped thumbnailsA fixed ratio or dimensions with object-fit: cover
Less loading shiftCorrect dimension attributes or equivalent reserved space

FAQ

Check whether CSS forces both dimensions to a ratio different from the image. Use height: auto for natural proportions, or object-fit inside a deliberately fixed box.

No. It tells the browser the expected slot size for resource selection. Your CSS still controls the displayed layout, so keep the two in agreement.

Usually not. If it is the likely LCP image, make it discoverable immediately, avoid loading=lazy and consider fetchpriority=high after measuring.

See also

Sources

Authoritative references this article was fact-checked against.

Tagsresponsive imagessrcsetsizespicture elementart directionAVIFWebPlazy loading

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Systems and Network Architect · Chief Technology Officer

Systems and network architect and Chief Technology Officer with more than two decades designing, building, and running production software, cloud and network architecture, Linux systems, and the bare metal underneath them, and lately working AI into the stack. A US Army veteran who served in Operation Iraqi Freedom. What I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts