Difficulty: Intermediate
Responsive images ensure that users download appropriately sized images for their device, saving bandwidth on mobile while delivering sharp visuals on high-resolution screens. Without responsive images, a mobile user on a 375px-wide screen downloads the same 2000px-wide image that a desktop user sees, wasting data and slowing page load. The HTML srcset and sizes attributes, the <picture> element, and CSS object-fit together provide a complete toolkit for responsive image handling.
The srcset attribute on an <img> tag provides the browser with a list of image sources at different widths or pixel densities. Using width descriptors (e.g., srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1200w"), you tell the browser the intrinsic width of each image. The browser then combines this with the sizes attribute and the viewport width to choose the most appropriate source. The sizes attribute tells the browser how large the image will be displayed: sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw" means the image occupies 33% of the viewport on desktop, 50% on tablet, and 100% on mobile.
For pixel density switching (serving 2x images on Retina screens), use density descriptors: srcset="logo.png 1x, logo@2x.png 2x, logo@3x.png 3x". The browser selects the source matching the device's pixel ratio. This is simpler than width descriptors but less flexible - it does not account for the image's display size.
The <picture> element enables art direction - serving entirely different images based on conditions. Unlike srcset (which lets the browser choose), <picture> gives you explicit control. Use <source> elements with media attributes to specify conditions: a wide landscape photo for desktop, a cropped portrait version for mobile. The browser uses the first matching source and falls back to the <img> element if none match.
CSS object-fit controls how an image fills its container, similar to background-size for background images. object-fit: cover scales the image to fill the container while maintaining aspect ratio (cropping edges if needed). object-fit: contain scales the image to fit entirely within the container (may leave empty space). object-fit: fill stretches the image to fill the container (distorts aspect ratio). Combined with a fixed-size container, object-fit ensures images look good regardless of their original dimensions.
<!-- Browser chooses the best image based on viewport + sizes -->
<img
src="image-800.jpg"
srcset="
image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w,
image-1600.jpg 1600w
"
sizes="
(min-width: 1024px) 33vw,
(min-width: 640px) 50vw,
100vw
"
alt="A scenic mountain landscape"
loading="lazy"
decoding="async"
>
<!-- How the browser decides:
1. Reads sizes to determine display width (e.g., 33vw on 1200px screen = 400px)
2. Multiplies by device pixel ratio (e.g., 400px * 2 = 800px on Retina)
3. Chooses the closest srcset image (image-800.jpg)
-->
srcset lists available image widths (400w, 800w, etc.). sizes tells the browser the display size at each breakpoint. The browser does the math: display width * pixel ratio = needed image width, then picks the closest match from srcset.
<!-- Simple 1x/2x/3x for logos and icons -->
<img
src="logo.png"
srcset="
logo.png 1x,
logo@2x.png 2x,
logo@3x.png 3x
"
alt="Company logo"
width="200"
height="60"
>
<!-- Profile avatar with density switching -->
<img
src="avatar-100.jpg"
srcset="
avatar-100.jpg 1x,
avatar-200.jpg 2x
"
alt="User avatar"
width="100"
height="100"
style="border-radius: 50%;"
>
Density descriptors (1x, 2x, 3x) are simpler than width descriptors. The browser matches the device's pixel ratio to the right source. Use this for fixed-size images (logos, icons, avatars) where the display size does not change.
<!-- Different crops for different screen sizes -->
<picture>
<!-- Wide landscape for desktop -->
<source
media="(min-width: 1024px)"
srcset="hero-wide.jpg"
>
<!-- Medium crop for tablet -->
<source
media="(min-width: 640px)"
srcset="hero-medium.jpg"
>
<!-- Tight portrait crop for mobile -->
<img
src="hero-mobile.jpg"
alt="Hero image showing the product in use"
loading="lazy"
>
</picture>
<!-- Format switching: serve modern formats with fallback -->
<picture>
<source type="image/avif" srcset="photo.avif">
<source type="image/webp" srcset="photo.webp">
<img src="photo.jpg" alt="Product photo" loading="lazy">
</picture>
The picture element gives explicit control over which image loads. Use media attributes for art direction (different crops per screen size). Use type attributes for format switching (AVIF > WebP > JPEG). The img is the fallback.
<style>
.card-image {
width: 100%;
height: 200px;
overflow: hidden;
}
/* Cover: fill container, crop excess */
.card-image img.cover {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
/* Contain: fit inside, may show background */
.card-image img.contain {
width: 100%;
height: 100%;
object-fit: contain;
background: #f1f5f9;
}
/* Avatar: always a circle, always cropped well */
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
object-position: top; /* Focus on face */
}
/* Full-width hero with aspect ratio */
.hero-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
</style>
<div class="card-image">
<img class="cover" src="photo.jpg" alt="Product">
</div>
<img class="avatar" src="person.jpg" alt="User photo">
<img class="hero-image" src="banner.jpg" alt="Hero banner">
object-fit: cover fills the container and crops excess (like background-size: cover). object-fit: contain fits the entire image inside the container. object-position controls the focal point. aspect-ratio maintains proportions without fixed height.
srcset, sizes, picture Element, object-fit, Art Direction, Resolution Switching