Why I Moved from bare <img> to <picture> with <img> – A Better Approach



This content originally appeared on DEV Community and was authored by Raheel Shan

Why I Moved from bare <img> to <picture> with <img> – A Better Approach

For years, i have use <img/> tag for displaying images on web pages. It is simple, straightforward and does the job well. However as i have shifted my focus on performance, SEO and UX, i realized the limitations of <img/> tag. After getting familiar with <picture/> tag and doing some experiments, i made a permanent switch and haven’t looked back.

In this post, i will explain why <picture/> is the better choice, how it solves major image-related issues, and the best practices to use it effectively.

The limitations of <img/>

The <img/> tag has been a web standard for decades, but with the latest web standards, it comes with a few drawbacks. Lets explore them one by one:

1. Lacks support for modern Web Formats like Webp

Modern image formats like WebP and AVIF offer better compression and quality than PNG or JPEG. However, using <img/> alone means:

  • Either you serve only WebP by risking incompatibility on older browsers 
  • Or you stick to traditional formats by sacrificing performance

2. No fallback handling

If an image fails to load due to a broken link, <img/> doesn’t automatically switch to another version. The only workaround is using JavaScript or an onerror event, which can be unreliable. Because if fallback image is not found javascript will take the web page into an infinite loop.

3. Potential for Layout Shift (CLS Issues)

Without explicitly setting width and height on <img /> tag causes cumulative layout shift (CLS) decreasing page load time which is a bad UX where content jumps when images load late.

4. Limited Responsiveness & Styling

Although <img/> tag allows setting width and height, these values are static unless overridden by CSS.

How <picture> Solves These Issues

The <picture/> tag provides a more flexible, SEO-friendly, and performance-optimized way to load images.

1. Better Format Handling with <source/>

With <picture>, you can specify multiple image formats and let the browser automatically choose the best one:

<picture>
    <source srcset="image.webp" type="image/webp">
    <img src="image.jpg" alt="Example Image">
</picture>
  • Browsers that support WebP will load image.webp
  • Others will fall back to image.jpg.
  • No JavaScript needed!

2. Fallback Support for Broken Images

Instead of using onerror, <picture> provides an elegant fallback:

<picture>
    <source srcset="/url/to/image/path.webp" type="image/webp">
    <img id="logo" src="/url/to/noimage/path.webp" alt="Logo Image">
</picture>
  • If source image is missing, the fallback is loaded automatically.

3. Preventing Layout Shift with Proper Dimensions

To avoid CLS, always define width and height inside <img>:

<picture>
    <source srcset="image.webp" type="image/webp">
    <img id="logo" src="fallback.jpg" width="120" height="100" class="w-[120px] h-[100px] object-cover">
</picture>
  • Browsers can reserve space before loading images, preventing shifts.
  • Tailwind classes (w-[120px] h-[100px]) ensure flexibility—overriding static width/height when needed. 
  • For responsiveness, you can replace w-[120px] h-[100px] with w-auto max-w-full.

Tip:  Use aspect-ratio for better image sizing. The below example ensures a consistent aspect ratio and prevents layout shifts.

<picture>
    <source srcset="feature-image.webp" type="image/webp">
    <img class="w-full aspect-[16/9] object-cover" src="image.webp" alt="Example">
</picture>

Tip: Use a Darker Background for Contrast. Applying bg-gray-800 or a similar dark background ensures images stand out on light themes. This makes the UX feel smoother by preventing blank spaces before images appear.

<picture>
    <source srcset="feature-image.webp" type="image/webp">
    <img class="w-32 h-32 bg-gray-800" src="image.webp" alt="Example">
</picture>

4. Lazy-Loading for Performance

To improve performance and reduce initial page load time, always use loading="lazy":

<picture>
    <source srcset="image.webp" type="image/webp">
    <img id="logo" src="fallback.jpg" width="120" height="100" class="w-[120px] h-[100px] object-cover" loading="lazy">
</picture>
  • Speeds up page load times by loading images only when they are about to enter the viewport.

5. Above-the-Fold vs. Below-the-Fold Images

Above-the-Fold Image (Eager Loading)

Above-the-fold images or images that are visible to viewport on page load should load immediately to ensure a good user experience:

<picture>
    <source srcset="hero-image.webp" type="image/webp">
    <img src="hero-image.jpg" alt="Hero Section Image" width="800" height="400" class="w-full h-auto object-cover" loading="eager">
</picture>

Below-the-Fold Image (Lazy Loading)

For images lower on the page, use loading=”lazy” to defer loading until needed:

<picture>
    <source srcset="feature-image.webp" type="image/webp">
    <img src="feature-image.jpg" alt="Feature Image" width="600" height="300" class="w-full h-auto object-cover" loading="lazy">
</picture>

6. JavaScript Image Previews

Since the <img> inside <picture> can have an id, JavaScript can easily update it for image previews:

document.getElementById("logo").src = URL.createObjectURL(fileInput.files[0]);
  • Allows previewing images before uploading them without breaking the fallback structure.

Lesson Crux

Here is the resultant component.

<picture>
    <source srcset="image.webp" type="image/webp">
    <source srcset="image.avif" type="image/avif">    
    <source srcset="image.jpg" type="image/jpg">
    <img src="noimage.webp" id="logo" alt="Logo Image" width="600" height="300" class="w-full h-auto object-cover" loading="lazy">
</picture>

<input name="logo" onchange="loadFile(event,'logo')" type="file" >

var loadFile = function(event, id) {
    var input = event.target;
    var file = input.files[0];

    if (!file) return; // Stop if no file is selected

    var output = document.getElementById(id);
    var source = output.closest('picture').querySelector('source');

    var objectURL = URL.createObjectURL(file);

    // Update the image preview
    output.src = objectURL;

    // Also update the <source> to ensure proper loading
    source.srcset = objectURL;

    output.onload = function() {
        URL.revokeObjectURL(objectURL); // Free memory
    };
};

 

Summary: Why <picture/> is the better

Switching from <img/> to <picture/> is one of the best decisions for any of my projects. Here’s why I always prefer <picture/> now:

  • SEO optimized images with WebP and Avif support.
  • Fallback image support without the need of javascript.
  • Prevents cumulative layout shifts and override for responsive images.
  • Lazy load for faster loading web pages.
  • Image preview available with javascript.

If you’re still using <img/> alone, I highly recommend upgrading to . It’s a small change with a huge impact! 🚀


This content originally appeared on DEV Community and was authored by Raheel Shan