Responsive Images Tutorial

Make your images look perfect on every device! Learn how to serve the right image size for phones, tablets, and desktops using modern HTML responsive image techniques.

Why Responsive Images Matter

Loading a 3000px wide image on a 375px phone screen wastes bandwidth, slows page loads, and drains battery life. Responsive images solve this by serving appropriately sized images based on the user’s device.

Benefits of responsive images:

The Problem with Fixed Images

Here’s what happens with traditional img tags:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fixed Image Problem</title>
</head>
<body>
<!-- Problematic example: this loads the same huge image on all devices -->
<img 
  src="hero-image-3000px.jpg" 
  alt="Beautiful landscape"
  style="width: 100%; height: auto;">

<!-- A 3000px image might be 2MB, but on a phone 
     you only need 400px which could be 100KB! -->
</body>
</html>

The browser downloads the full 2MB image even on a phone that only displays it at 400px wide. That’s wasteful!

Solution 1: The srcset Attribute

The srcset attribute lets you provide multiple image sizes and let the browser choose:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>srcset Example</title>
</head>
<body>
<img 
  src="landscape-800w.jpg"
  srcset="landscape-400w.jpg 400w,
          landscape-800w.jpg 800w,
          landscape-1200w.jpg 1200w,
          landscape-2000w.jpg 2000w"
  alt="Beautiful mountain landscape"
  style="width: 100%; height: auto;">

<!-- 
  Browser picks the best image based on:
  - Screen width
  - Device pixel ratio
  - Network speed (sometimes)
-->
</body>
</html>

How srcset works:

Using sizes Attribute for Better Control

The sizes attribute tells the browser how wide the image will be displayed:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>srcset with sizes</title>
<style>
  .hero-image {
    width: 100%;
    height: auto;
  }
  
  @media (min-width: 768px) {
    .hero-image {
      width: 50%;
      float: left;
      margin-right: 20px;
    }
  }
</style>
</head>
<body>
<article>
  <img 
    src="product-800w.jpg"
    srcset="product-400w.jpg 400w,
            product-800w.jpg 800w,
            product-1200w.jpg 1200w"
    sizes="(min-width: 768px) 50vw,
           100vw"
    alt="Premium headphones"
    class="hero-image">
  
  <h1>Premium Wireless Headphones</h1>
  <p>Experience audio like never before with our latest model...</p>
</article>

<!-- 
  sizes explanation:
  - On screens 768px+: image takes 50% viewport width (50vw)
  - On smaller screens: image takes full viewport width (100vw)
  - Browser picks the smallest image that fits
-->
</body>
</html>

sizes syntax:

The <picture> Element for Art Direction

Use <picture> when you need different images for different screen sizes (art direction):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Picture Element Example</title>
</head>
<body>
<picture>
  <!-- Large screens: wide landscape photo -->
  <source 
    media="(min-width: 1200px)"
    srcset="hero-wide-1600w.jpg 1600w,
            hero-wide-2400w.jpg 2400w">
  
  <!-- Medium screens: standard landscape -->
  <source 
    media="(min-width: 768px)"
    srcset="hero-medium-1000w.jpg 1000w,
            hero-medium-1400w.jpg 1400w">
  
  <!-- Small screens: portrait crop -->
  <source 
    media="(max-width: 767px)"
    srcset="hero-mobile-600w.jpg 600w,
            hero-mobile-800w.jpg 800w">
  
  <!-- Fallback for browsers that don't support picture -->
  <img 
    src="hero-medium-1000w.jpg" 
    alt="Modern office workspace"
    style="width: 100%; height: auto;">
</picture>

<!-- 
  Perfect for:
  - Different crops for different screens
  - Showing different content (portrait vs landscape)
  - Highlighting different subjects
-->
</body>
</html>

Key differences:

Modern Image Formats with <picture>

Serve WebP for modern browsers, with JPEG fallback:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Image Formats</title>
</head>
<body>
<picture>
  <!-- Try AVIF first (best compression, newest) -->
  <source 
    type="image/avif"
    srcset="product-400.avif 400w,
            product-800.avif 800w,
            product-1200.avif 1200w"
    sizes="(min-width: 768px) 50vw, 100vw">
  
  <!-- Try WebP next (great compression, wide support) -->
  <source 
    type="image/webp"
    srcset="product-400.webp 400w,
            product-800.webp 800w,
            product-1200.webp 1200w"
    sizes="(min-width: 768px) 50vw, 100vw">
  
  <!-- JPEG fallback for older browsers -->
  <img 
    src="product-800.jpg"
    srcset="product-400.jpg 400w,
            product-800.jpg 800w,
            product-1200.jpg 1200w"
    sizes="(min-width: 768px) 50vw, 100vw"
    alt="Professional camera"
    style="width: 100%; height: auto;">
</picture>

<!-- 
  File size comparison example:
  - JPEG: 200 KB
  - WebP: 120 KB (40% smaller!)
  - AVIF: 80 KB (60% smaller!)
-->
</body>
</html>

Modern formats can be 30-60% smaller than JPEG with the same visual quality!

Create a responsive photo gallery using CSS Grid and srcset:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Responsive Image Gallery</title>
<style>
  .gallery {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 20px;
    padding: 20px;
  }
  
  .gallery-item {
    position: relative;
    overflow: hidden;
    border-radius: 8px;
    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
    transition: transform 0.3s;
  }
  
  .gallery-item:hover {
    transform: translateY(-5px);
    box-shadow: 0 8px 12px rgba(0,0,0,0.2);
  }
  
  .gallery-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }
  
  .gallery-caption {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(0,0,0,0.7);
    color: white;
    padding: 15px;
    transform: translateY(100%);
    transition: transform 0.3s;
  }
  
  .gallery-item:hover .gallery-caption {
    transform: translateY(0);
  }
</style>
</head>
<body>
<div class="gallery">
  <div class="gallery-item">
    <img 
      src="photo1-600w.jpg"
      srcset="photo1-400w.jpg 400w,
              photo1-600w.jpg 600w,
              photo1-800w.jpg 800w"
      sizes="(min-width: 1200px) 25vw,
             (min-width: 768px) 33vw,
             (min-width: 500px) 50vw,
             100vw"
      alt="Sunset over mountains"
      loading="lazy">
    <div class="gallery-caption">
      <h3>Mountain Sunset</h3>
      <p>Rocky Mountains, Colorado</p>
    </div>
  </div>
  
  <div class="gallery-item">
    <img 
      src="photo2-600w.jpg"
      srcset="photo2-400w.jpg 400w,
              photo2-600w.jpg 600w,
              photo2-800w.jpg 800w"
      sizes="(min-width: 1200px) 25vw,
             (min-width: 768px) 33vw,
             (min-width: 500px) 50vw,
             100vw"
      alt="Ocean waves crashing"
      loading="lazy">
    <div class="gallery-caption">
      <h3>Pacific Waves</h3>
      <p>Big Sur, California</p>
    </div>
  </div>
  
  <div class="gallery-item">
    <img 
      src="photo3-600w.jpg"
      srcset="photo3-400w.jpg 400w,
              photo3-600w.jpg 600w,
              photo3-800w.jpg 800w"
      sizes="(min-width: 1200px) 25vw,
             (min-width: 768px) 33vw,
             (min-width: 500px) 50vw,
             100vw"
      alt="Forest with morning fog"
      loading="lazy">
    <div class="gallery-caption">
      <h3>Misty Forest</h3>
      <p>Redwood National Park</p>
    </div>
  </div>
  
  <!-- Add more gallery items... -->
</div>
</body>
</html>

Notice the loading="lazy" attribute — it delays loading images until they’re about to enter the viewport, improving initial page load!

Lazy Loading Images

Use native lazy loading to improve performance:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lazy Loading Images</title>
<style>
  img {
    width: 100%;
    height: auto;
    margin-bottom: 20px;
  }
</style>
</head>
<body>
<!-- Images above the fold: load immediately -->
<img 
  src="hero-1200w.jpg"
  srcset="hero-600w.jpg 600w,
          hero-1200w.jpg 1200w,
          hero-1800w.jpg 1800w"
  sizes="100vw"
  alt="Hero image"
  loading="eager">

<!-- Images below the fold: lazy load -->
<img 
  src="content1-800w.jpg"
  srcset="content1-400w.jpg 400w,
          content1-800w.jpg 800w"
  sizes="(min-width: 768px) 50vw, 100vw"
  alt="Content image 1"
  loading="lazy">

<img 
  src="content2-800w.jpg"
  srcset="content2-400w.jpg 400w,
          content2-800w.jpg 800w"
  sizes="(min-width: 768px) 50vw, 100vw"
  alt="Content image 2"
  loading="lazy">

<!-- 
  loading attribute values:
  - "eager": Load immediately (default)
  - "lazy": Load when near viewport
  
  Use eager for critical above-the-fold images
  Use lazy for everything else
-->
</body>
</html>

Lazy loading tips:

High DPI / Retina Displays

Serve higher resolution images for high-density displays:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Retina Images</title>
</head>
<body>
<!-- Method 1: Using srcset with x descriptor -->
<img 
  src="logo.png"
  srcset="logo.png 1x,
          [email protected] 2x,
          [email protected] 3x"
  alt="Company Logo"
  width="200"
  height="80">

<!-- 
  x descriptor:
  - 1x = Standard DPI (96 dpi)
  - 2x = High DPI / Retina (192 dpi)
  - 3x = Ultra high DPI (288 dpi)
-->

<!-- Method 2: Combining width and pixel density -->
<img 
  src="icon-100w.png"
  srcset="icon-100w.png 100w,
          icon-200w.png 200w,
          icon-400w.png 400w"
  sizes="100px"
  alt="App Icon">

<!-- 
  Browser automatically picks:
  - icon-100w.png on 1x displays
  - icon-200w.png on 2x displays (Retina)
  - icon-400w.png on 3x displays
-->
</body>
</html>

Aspect Ratio and Layout Shifts

Prevent layout shifts by reserving space for images:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aspect Ratio Images</title>
<style>
  /* Method 1: Using aspect-ratio property */
  .image-container-1 img {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
  }
  
  /* Method 2: Using padding-bottom trick */
  .image-container-2 {
    position: relative;
    width: 100%;
    padding-bottom: 56.25%; /* 16:9 = 9/16 = 0.5625 */
    overflow: hidden;
  }
  
  .image-container-2 img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
</style>
</head>
<body>
<!-- Modern browsers: aspect-ratio -->
<div class="image-container-1">
  <img 
    src="landscape-800w.jpg"
    srcset="landscape-400w.jpg 400w,
            landscape-800w.jpg 800w,
            landscape-1200w.jpg 1200w"
    sizes="100vw"
    alt="Mountain landscape"
    loading="lazy">
</div>

<!-- Older browser support: padding-bottom -->
<div class="image-container-2">
  <img 
    src="landscape-800w.jpg"
    srcset="landscape-400w.jpg 400w,
            landscape-800w.jpg 800w,
            landscape-1200w.jpg 1200w"
    sizes="100vw"
    alt="Mountain landscape"
    loading="lazy">
</div>

<!-- Best practice: Specify width and height -->
<img 
  src="product-600w.jpg"
  srcset="product-400w.jpg 400w,
          product-600w.jpg 600w,
          product-900w.jpg 900w"
  sizes="(min-width: 768px) 50vw, 100vw"
  alt="Product photo"
  width="600"
  height="400"
  loading="lazy">

<!-- 
  width/height preserves aspect ratio even if CSS changes size
  Prevents Cumulative Layout Shift (CLS)
-->
</body>
</html>

Common Mistakes to Avoid

Mistake 1: Not Including Fallback

Problematic example:

<img srcset="photo-400w.jpg 400w, photo-800w.jpg 800w">

Improved example:

<img 
  src="photo-800w.jpg"
  srcset="photo-400w.jpg 400w, photo-800w.jpg 800w"
  alt="Description">

Why: Older browsers need the src attribute as fallback.

Mistake 2: Wrong sizes Syntax

Problematic example:

<img 
  srcset="photo-400w.jpg 400w, photo-800w.jpg 800w"
  sizes="400px, 800px">

Improved example:

<img 
  srcset="photo-400w.jpg 400w, photo-800w.jpg 800w"
  sizes="(min-width: 768px) 50vw, 100vw">

Why: sizes needs media queries, not just numbers.

Mistake 3: Lazy Loading Everything

Problematic example:

<img src="hero.jpg" loading="lazy" alt="Hero">

Improved example:

<img src="hero.jpg" loading="eager" alt="Hero">
<img src="content.jpg" loading="lazy" alt="Content">

Why: Don’t lazy load above-the-fold images—it slows down initial render!

Mistake 4: Missing Alt Text

Problematic example:

<img src="product.jpg" srcset="...">

Improved example:

<img src="product.jpg" srcset="..." alt="Blue running shoes">

Why: Alt text is required for accessibility and SEO.

Mistake 5: Not Optimizing Source Images

Problematic example: Using 5MB uncompressed images

Improved example: Compress images before creating responsive versions (use tools like ImageOptim, Squoosh, or Sharp)

Why: Even with responsive images, you still need to optimize each version!

Try It Yourself

Ready to practice responsive images? Try these challenges:

Challenge 1: Basic Responsive Image (Beginner)

Create a page with:

Build a photo gallery with:

Challenge 3: Advanced Image Optimization (Advanced)

Create a complete responsive image solution:

Bonus: Use a build tool or image CDN (like Cloudinary or Imgix) to automatically generate multiple sizes and formats!

What You Learned

Congratulations! You now know how to:

Next Steps

Now that you master responsive images, explore these related tutorials:

Ready to implement responsive images? Start experimenting in our interactive HTML editor!

Back to all tutorials