Responsive Images: srcset, sizes, picture

Reviewed & published by Brayan K

Responsive images use the srcset and sizes attributes, plus the <picture> element, to let the browser pick the best image file for each device's screen size, resolution, and supported formats, saving bandwidth without sacrificing sharpness.

Part of the free HTML & CSS course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

By the end of this lesson you'll serve every visitor an image sized for their screen, swap crops and modern formats with <picture>, lazy-load what's off-screen, and reserve space so your page never jumps while images load.

💡 Think of It Like This

Responsive images are like a coffee shop offering small, medium and large cups. You wouldn't pour a venti into someone who asked for an espresso — you'd waste coffee and they couldn't drink it all. A 4000-pixel photo sent to a phone is exactly that waste: megabytes the screen can't even show, paid for by the visitor's data plan and your load time.

With srcset you hand the browser the whole menu of sizes and let it order the cup that fits the cup-holder (sizes). The phone sips a 400px file; the 4K monitor gets the 2000px one. Everyone gets a full cup, nobody pays for spillage — and because you reserved the right-sized cup-holder up front (aspect-ratio), the table never wobbles while the drink arrives.

1. srcset & sizes — Resolution Switching

Images are usually the heaviest thing on a page — often 60–80% of its total weight. Resolution switching means shipping the same picture at several file sizes and letting the browser download whichever fits the screen. You list the files in srcset, each tagged with its real pixel width using the w descriptor (800w means "this file is 800 pixels wide").

On its own, srcset isn't enough — the browser chooses the file before it has laid out the page, so it can't yet know how wide the image will appear. That's the job of sizes: it tells the browser, for each viewport width, how wide the image's slot will be. The browser combines sizes with the device's pixel density to pick the smallest file that still looks sharp.

Run the worked example, then resize the preview. Read every comment — they state exactly what each attribute does.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>srcset + sizes</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 20px; max-width: 800px; margin: 0 auto; }
    img  { width: 100%; height: auto; border-radius: 8px; display: block; }
    .note { background:#e0f2fe; color:#075985; padding:12px 16px; border-radius:8px; margin:16px 0; }
    code { background:#bae6fd; padding:2px 6px; border-radius:4px; }
  </style>
</head>
<body>
  <h1>Resolution switching</h1>

  <img
    src="https://picsum.photos/800/400"
    srcset="https://picsum.photos/400/200 400w,
            https://picsum.photos/800/400 800w,
            https://picsum.photos/1200/600 1200w"
    sizes="(max-width: 600px) 100vw,
           (max-width: 1000px) 80vw,
           60vw"
    alt="A landscape photo that switches resolution by screen size">

  <!-- srcset = the menu of files, each with its REAL width:
         400w  => the 400px-wide file
         800w  => the 800px-wide file
         1200w => the 1200px-wide file
       sizes = how WIDE the image's slot will be at each viewport:
         (max-width: 600px) 100vw  => on phones, image fills the screen
         (max-width: 1000px) 80vw  => on tablets, 80% of the screen
         60vw                      => otherwise, 60% of the screen
       The browser does the maths and downloads the SMALLEST file that
       still looks sharp for that slot + the device's pixel density. -->

  <div class="note">
    Resize this preview. On a narrow window the browser fetches the 
    <code>400w</code> file; widen it and it steps up to <code>800w</code> then 
    <code>1200w</code> — automatically, no JavaScript.
  </div>

  <!-- ✅ Expected: one image that always fills its slot, but the file the
       browser actually downloads gets bigger as the window gets wider. -->
  <!-- ✅ Expected result, measured in a real browser:
     img -> display: block
     .note -> padding-top: 12px
  -->
</body>
</html>

2. The <picture> Element — Art Direction & Modern Formats

Where srcset serves the same image at different sizes, <picture> lets you serve different images entirely. This is called art direction: a wide landscape crop on desktop, but a tight square or portrait crop on mobile so the subject stays large enough to see. You wrap one or more <source> elements and a final fallback <img> inside it.

The browser reads the <source> elements top to bottom and uses the first one that matches. Each source can match on a media query (for art direction) or a type (for formats like image/avif and image/webp). The trailing <img> is mandatory — it's both the fallback for old browsers and the element that actually carries the alt text.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>picture element</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 20px; max-width: 700px; margin: 0 auto; }
    img  { width: 100%; height: auto; border-radius: 8px; display: block; }
    .note { background:#e0f2fe; color:#075985; padding:12px 16px; border-radius:8px; margin:16px 0; }
    pre  { background:#1e293b; color:#e2e8f0; padding:16px; border-radius:8px; overflow-x:auto; font-size:.85rem; }
  </style>
</head>
<body>
  <h1>Art direction with &lt;picture&gt;</h1>

  <picture>
    <!-- Wide screens (>= 600px): a LANDSCAPE crop -->
    <source media="(min-width: 600px)" srcset="https://picsum.photos/700/300">
    <!-- Otherwise the browser falls through to the <img>: a SQUARE crop -->
    <img src="https://picsum.photos/360/360" alt="A photo cropped to suit the screen">
  </picture>

  <div class="note">
    Widen the preview past 600px and the crop becomes a wide landscape;
    narrow it and you get the square version. Same subject, different framing.
  </div>

  <h2>Modern formats with a fallback</h2>
  <p>In a real project you'd offer AVIF and WebP first, then a JPEG the
     browser always understands:</p>

  <pre>&lt;picture&gt;
  &lt;source type="image/avif" srcset="hero.avif"&gt;   &lt;!-- best compression --&gt;
  &lt;source type="image/webp" srcset="hero.webp"&gt;   &lt;!-- still smaller than JPEG --&gt;
  &lt;img src="hero.jpg" alt="Hero"
       width="1200" height="600"&gt;                 &lt;!-- universal fallback --&gt;
&lt;/picture&gt;</pre>

  <!-- ✅ Expected: the photo above re-crops at the 600px breakpoint, and the
       code block shows the AVIF -> WebP -> JPEG fallback chain. The browser
       downloads ONLY the first <source> it understands. -->
  <!-- ✅ Expected result, measured in a real browser:
     img -> display: block
     .note -> padding-top: 12px
  -->
</body>
</html>
<picture>
  <source type="image/avif" srcset="hero.avif">   <!-- best compression -->
  <source type="image/webp" srcset="hero.webp">   <!-- still smaller than JPEG -->
  <img src="hero.jpg" alt="Hero"
       width="1200" height="600">                 <!-- universal fallback -->
</picture>

3. loading="lazy" & decoding="async" — Faster Pages

loading="lazy" tells the browser to defer downloading an image until the user scrolls near it. On a long gallery that can turn dozens of downloads into just the two or three that are actually on screen, slashing your initial page weight. The catch: never lazy-load an image that's visible on first paint (your hero) — deferring it delays your Largest Contentful Paint and hurts Core Web Vitals.

decoding="async" is a smaller win that pairs nicely: it lets the browser decode the image off the main thread so a big picture can't briefly freeze scrolling or interaction. Use it on content images you're not racing to paint immediately.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>lazy loading</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 20px; max-width: 640px; margin: 0 auto; }
    .gallery { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
    .gallery img { width: 100%; height: 180px; object-fit: cover; border-radius: 8px; }
    .tip { background:#fff7ed; color:#9a3412; border-left:4px solid #f97316; padding:12px 16px; border-radius:8px; margin:16px 0; }
  </style>
</head>
<body>
  <h1>Lazy-loaded gallery</h1>

  <div class="tip">
    The first row is on screen, so it loads now. The rows below have
    loading="lazy" + decoding="async" and only download as you scroll to them.
  </div>

  <div class="gallery">
    <!-- Above the fold: load eagerly (the default). No loading="lazy" here. -->
    <img src="https://picsum.photos/400/300?random=1" alt="Photo 1" width="400" height="300">
    <img src="https://picsum.photos/400/300?random=2" alt="Photo 2" width="400" height="300">

    <!-- Below the fold: defer the download AND decode off the main thread. -->
    <img src="https://picsum.photos/400/300?random=3" alt="Photo 3" width="400" height="300" loading="lazy" decoding="async">
    <img src="https://picsum.photos/400/300?random=4" alt="Photo 4" width="400" height="300" loading="lazy" decoding="async">
    <img src="https://picsum.photos/400/300?random=5" alt="Photo 5" width="400" height="300" loading="lazy" decoding="async">
    <img src="https://picsum.photos/400/300?random=6" alt="Photo 6" width="400" height="300" loading="lazy" decoding="async">
  </div>

  <!-- ✅ Expected: the grid renders instantly because width/height reserve the
       boxes; the lower images stream in only as they scroll into view. -->
  <!-- ✅ Expected result, measured in a real browser:
     .gallery -> display: grid
     .gallery img -> object-fit: cover
  -->
</body>
</html>

4. width/height + aspect-ratio — Stop the Layout Shift

Ever seen text leap down the page as an image pops in? That's Cumulative Layout Shift (CLS), a Core Web Vital you can score badly on. It happens because the browser doesn't know how tall an image will be until the file arrives, so it reserves no space and then shoves everything down once it loads.

Two fixes, used together. First, always put width and height attributes on every <img> — even when CSS makes it fluid with width: 100%; height: auto;. The browser reads them as a ratio and reserves the right box up front. Second, for CSS-driven boxes (or placeholders), set the aspect-ratio property so the container holds its shape before anything loads.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>aspect-ratio</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 20px; max-width: 700px; margin: 0 auto; }

    .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px,1fr)); gap: 16px; }
    .card { border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; }

    /* width:100% + height:auto keeps the image fluid... */
    .card img { width: 100%; height: auto; display: block; object-fit: cover; }

    /* ...and aspect-ratio reserves the box BEFORE the file loads => no CLS. */
    .r-16-9 img { aspect-ratio: 16 / 9; }
    .r-1-1  img { aspect-ratio: 1 / 1; }
    .r-4-3  img { aspect-ratio: 4 / 3; }

    .label { padding: 8px 12px; font-family: monospace; font-size: .8rem; background:#f1f5f9; color:#475569; }

    /* A placeholder uses the SAME ratio so it holds exact space while loading. */
    .ph { aspect-ratio: 16 / 9; background: linear-gradient(135deg,#e0f2fe,#bae6fd);
          display:flex; align-items:center; justify-content:center; color:#075985; font-weight:600; }
  </style>
</head>
<body>
  <h1>aspect-ratio reserves space</h1>

  <div class="grid">
    <div class="card r-16-9">
      <img src="https://picsum.photos/400/225?random=21" alt="16:9 crop" width="400" height="225">
      <div class="label">aspect-ratio: 16 / 9</div>
    </div>
    <div class="card r-4-3">
      <img src="https://picsum.photos/400/300?random=22" alt="4:3 crop" width="400" height="300">
      <div class="label">aspect-ratio: 4 / 3</div>
    </div>
    <div class="card r-1-1">
      <img src="https://picsum.photos/400/400?random=23" alt="1:1 crop" width="400" height="400">
      <div class="label">aspect-ratio: 1 / 1</div>
    </div>
  </div>

  <h2 style="margin-top:24px;">A placeholder holding its box</h2>
  <div class="ph">16 / 9 placeholder — same shape as the real image</div>

  <!-- ✅ Expected: every box is the correct shape the instant the page renders,
       so nothing jumps as the photos stream in. That is a CLS score of ~0. -->
  <!-- ✅ Expected result, measured in a real browser:
     .grid -> display: grid
     .card -> overflow: hidden
     .card img -> display: block
     .r-16-9 img -> aspect-ratio: 16 / 9
     .card -> count: 3
  -->
</body>
</html>

5. object-fit & object-position — Fit Any Box

When you force an image into a fixed box, its own shape rarely matches. By default it stretches and looks squashed. object-fit fixes that — it's like background-size, but for a real <img>. cover fills the box and crops the overflow (no distortion); contain fits the whole image inside and may leave gaps; fill stretches it (the ugly default).

When cover crops, it crops from the centre by default — which can chop off a head. object-position lets you steer which part survives the crop, e.g. object-position: top; to keep faces in frame.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>object-fit</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 20px; max-width: 760px; margin: 0 auto; }
    .row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px,1fr)); gap: 16px; }
    figure { margin: 0; }
    figcaption { font-family: monospace; font-size: .8rem; color:#475569; margin-top:6px; text-align:center; }

    /* Every box is the SAME fixed size so we can compare how each fit behaves. */
    .box { width: 100%; height: 150px; border: 1px solid #e2e8f0; border-radius: 8px; background:#f1f5f9; }

    .fit-cover   { object-fit: cover; }     /* fill + crop, no distortion (best default) */
    .fit-contain { object-fit: contain; }   /* whole image, letterboxed gaps */
    .fit-fill    { object-fit: fill; }       /* stretched to fit, DISTORTS */
    .fit-top     { object-fit: cover; object-position: top; } /* crop, but keep the top */
  </style>
</head>
<body>
  <h1>object-fit in a fixed 150px box</h1>

  <div class="row">
    <figure><img class="box fit-cover"   src="https://picsum.photos/300/500?random=31" alt="cover"><figcaption>cover</figcaption></figure>
    <figure><img class="box fit-contain" src="https://picsum.photos/300/500?random=31" alt="contain"><figcaption>contain</figcaption></figure>
    <figure><img class="box fit-fill"    src="https://picsum.photos/300/500?random=31" alt="fill"><figcaption>fill (distorts)</figcaption></figure>
    <figure><img class="box fit-top"     src="https://picsum.photos/300/500?random=31" alt="cover top"><figcaption>cover + top</figcaption></figure>
  </div>

  <!-- ✅ Expected: same photo, same box, four results — cover fills neatly,
       contain shows gaps, fill looks squashed, and "cover + top" crops from
       the bottom so the top of the image stays visible. -->
  <!-- ✅ Expected result, measured in a real browser:
     .row -> display: grid
     .fit-cover -> object-fit: cover
     .fit-contain -> object-fit: contain
     .fit-fill -> object-fit: fill
     .box -> count: 4
  -->
</body>
</html>

🎯 Your Turn #1 — Wire up srcset + sizes

The image below only has a single src, so every device downloads the big file. Give the browser a menu of sizes to choose from. Fill in the blanks marked ___, then run it and check the expected result in the comments.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Your Turn 1</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 20px; max-width: 800px; margin: 0 auto; }
    img  { width: 100%; height: auto; border-radius: 8px; display: block; }
  </style>
</head>
<body>
  <h1>Make this image responsive</h1>

  <!-- 🎯 YOUR TURN — fill in the blanks marked ___ -->
  <img
    src="https://picsum.photos/1200/600"

    /* 1) Offer THREE files with their real widths (the w descriptor). */
    srcset="___"
    /* 👉 use: "https://picsum.photos/400/200 400w,
                https://picsum.photos/800/400 800w,
                https://picsum.photos/1200/600 1200w" */

    /* 2) Tell the browser the slot is full-width on phones, else 70%. */
    sizes="___"
    /* 👉 use: "(max-width: 600px) 100vw, 70vw" */

    alt="A responsive landscape photo">

  <!-- ✅ Expected: resize the preview — on a narrow window the browser fetches
       the 400w file, and it steps up to 800w then 1200w as you widen it. -->
</body>
</html>

🎯 Your Turn #2 — Art direction + a no-CLS box

Build a <picture> that swaps crop at 600px, give the fallback <img> its dimensions so the page doesn't jump, and crop it to a fixed shape without distortion. Fill in each ___ and verify against the comments.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Your Turn 2</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 20px; max-width: 700px; margin: 0 auto; }
    .hero { aspect-ratio: 16 / 9; border-radius: 12px; overflow: hidden; }

    /* 3) Make the image FILL the .hero box and crop overflow, no stretching. */
    .hero img { width: 100%; height: 100%; object-fit: ___; display: block; }
    /* 👉 replace ___ with  cover */
  </style>
</head>
<body>
  <h1>Art-directed hero</h1>

  <div class="hero">
    <picture>
      <!-- 1) Wide screens (>= 600px) get the LANDSCAPE crop. -->
      <source media="___" srcset="https://picsum.photos/800/450">
      <!-- 👉 replace ___ with  (min-width: 600px) -->

      <!-- 2) The fallback img needs width + height so the box is reserved. -->
      <img src="https://picsum.photos/450/450"
           ___
           alt="A hero that re-crops by screen width">
      <!-- 👉 add the attributes:  width="450" height="450" -->
    </picture>
  </div>

  <!-- ✅ Expected: at >= 600px the hero shows a wide crop, narrower shows the
       square crop, the box never jumps as it loads, and the image fills the
       16/9 frame without distortion. -->
</body>
</html>

🧩 Mini-Challenge — A performant card from scratch

Support is faded now — only an outline is given. Build one image card that uses everything from this lesson. Lean on the worked examples in sections 1–5 if you get stuck.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Mini-Challenge</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 20px; max-width: 480px; margin: 0 auto; }

    /* 🧩 MINI-CHALLENGE: a fast, shift-free image card
       1. Style a .card with a border and rounded corners (overflow: hidden)
       2. Give .card .thumb an aspect-ratio (e.g. 16 / 9) so its box is
          reserved BEFORE the image loads (this kills CLS)
       3. Make .card .thumb img fill that box with
          width:100%; height:100%; object-fit: cover;
       4. On the <img> itself add:
            - srcset with at least two widths (e.g. 400w and 800w)
            - sizes describing the slot (the card is ~448px wide here)
            - width and height attributes (intrinsic ratio for the browser)
            - loading="lazy" and decoding="async" (it's below the fold)

       ✅ Expected: the card's image box is the right shape instantly (no jump),
          the photo fills it without distortion, and the browser downloads the
          smaller file on a narrow screen. */

    /* your CSS here */
  </style>
</head>
<body>

  <!-- your markup here: a .card containing a .thumb wrapper with one <img>,
       plus a little caption underneath -->

</body>
</html>

⚠️ Common Errors (and the fix)

📋 Quick Reference

GoalUse this
Serve sizes of the same imagesrcset="a.jpg 400w, b.jpg 800w"
Describe the display slotsizes="(max-width:600px) 100vw, 60vw"
Fixed-size image on retinasrcset="i.png 1x, [email protected] 2x"
Different crop per viewport<source media="(min-width:600px)" srcset="…">
Modern format + fallback<source type="image/avif" srcset="…">
Defer off-screen downloadloading="lazy" decoding="async"
Reserve space (no CLS)width="800" height="450" + aspect-ratio: 16/9
Fill a box without distortionobject-fit: cover;
Steer the cropobject-position: top;

🎉 Lesson Complete

You can now ship images that are fast, sharp on every screen, and don't shift the layout. The essentials:

Practice quiz

What does the `w` descriptor in srcset describe?

  • The pixel density of the screen
  • The weight in kilobytes
  • The real pixel width of the image file
  • The widget type

Answer: The real pixel width of the image file. The w descriptor states each file's real pixel width, e.g. image-800.jpg 800w means that file is 800px wide.

Why must you pair `w` descriptors in srcset with a `sizes` attribute?

  • To tell the browser how wide the image slot will be so it can pick the right file
  • To enable lazy loading
  • To set the aspect ratio
  • To add alt text

Answer: To tell the browser how wide the image slot will be so it can pick the right file. sizes tells the browser the slot width at each viewport; without it the browser assumes 100vw and often over-downloads.

When should you reach for the <picture> element instead of plain srcset?

  • When only the resolution changes
  • When the image is decorative
  • When you need lazy loading
  • When the actual image changes — a different crop (art direction) or format

Answer: When the actual image changes — a different crop (art direction) or format. Use <picture> for art direction (different crops) or format swaps (AVIF/WebP with a JPEG fallback); srcset is for same-image resolution switching.

What is the role of the trailing <img> inside a <picture> element?

  • It is optional decoration
  • It is the mandatory fallback and carries the alt text
  • It sets the page background
  • It must be empty

Answer: It is the mandatory fallback and carries the alt text. The <img> is mandatory: it is the fallback when no <source> matches and it carries the alt text.

How does the browser choose among the <source> elements in a <picture>?

  • It uses the first <source> that matches, top to bottom
  • It uses the last one
  • It uses the smallest file
  • It picks randomly

Answer: It uses the first <source> that matches, top to bottom. The browser reads sources top to bottom and uses the first one that matches its media query or supported type.

Which descriptor is best for a fixed-size logo on retina screens?

  • The w descriptor
  • The vw descriptor
  • The x descriptor (e.g. 2x)
  • The dpi descriptor

Answer: The x descriptor (e.g. 2x). The x descriptor (e.g. [email protected] 2x) describes pixel density and suits fixed-size images like icons and logos.

Which attribute defers an off-screen image's download until you scroll near it?

  • decoding="async"
  • loading="lazy"
  • fetchpriority="low"
  • defer

Answer: loading="lazy". loading="lazy" delays downloading until the image nears the viewport — ideal for below-the-fold images.

Should you set width and height attributes even when CSS makes the image fluid with width:100%?

  • No, they are ignored
  • Only on the hero image
  • Only for SVGs
  • Yes — they give the intrinsic ratio so the browser reserves space and avoids CLS

Answer: Yes — they give the intrinsic ratio so the browser reserves space and avoids CLS. Even with width:100%; height:auto, the attributes provide the intrinsic aspect ratio so the browser reserves the right box and prevents layout shift.

Which object-fit value fills the box and crops the overflow without distorting the image?

  • fill
  • cover
  • contain
  • scale-down

Answer: cover. object-fit: cover fills the box and crops overflow with no distortion; contain letterboxes and fill stretches.

When `object-fit: cover` crops an image, which property steers which part stays visible?

  • object-align
  • background-position
  • object-position
  • crop-anchor

Answer: object-position. object-position (e.g. object-position: top) controls which part of the image survives the crop.

Continue this course

Frequently asked questions

When should I use srcset versus the <picture> element?

Use srcset (with sizes) when you are serving the same image at different sizes and just want the browser to pick the most efficient file — this is the common case for almost every content image. Use <picture> when you need to change the image itself depending on the situation: a different crop on mobile than desktop (art direction), or a modern format like WebP/AVIF with a JPEG fallback. A quick rule of thumb: if the only thing changing is the resolution, reach for srcset; if the actual pixels (crop, format) change, reach for <picture>.

What is the difference between the w and x descriptors in srcset?

The w descriptor tells the browser the real pixel width of each file — image-800.jpg 800w means that file is 800 pixels wide. You pair w descriptors with the sizes attribute so the browser can work out which file fits the slot. The x descriptor describes pixel density instead — [email protected] 2x means 'use this on a 2x retina screen'. Use w for fluid content images that resize with the layout, and x for fixed-size images like icons and logos that are always displayed at one CSS size.

Why does my page jump around while images load?

That jump is Cumulative Layout Shift (CLS), and it happens when the browser does not know how tall an image will be until the file downloads, so it reserves zero height and then shoves everything down once the image arrives. The fix is to tell the browser the dimensions up front: put width and height attributes on every <img>, or set an aspect-ratio in CSS. The browser then reserves the correct box immediately and the image fills it in place — no jump.

Do I still need width and height attributes if my image is fluid with width: 100%?

Yes — and this surprises people. Even when CSS overrides the displayed width (img { width: 100%; height: auto; }), the width and height attributes still give the browser the intrinsic aspect ratio, which it uses to reserve the right amount of vertical space before the file loads. Modern browsers combine the attributes with your height: auto to compute the correct box. Leave them off and you lose your CLS protection, so always include them.

Is loading="lazy" safe to put on every image?

Almost — but never put it on your hero or any image that is visible when the page first loads (above the fold). Lazy loading defers the download until the image is near the viewport, which is perfect for off-screen images but delays your Largest Contentful Paint (LCP) if applied to the first thing the user sees. Rule: loading="lazy" for below-the-fold images, and let above-the-fold images load eagerly (the default), optionally with fetchpriority="high" on the hero.

Are WebP and AVIF safe to use in production?

Yes, with a fallback. WebP is supported in every current browser, and AVIF is supported in all the major ones too, but you should never serve them alone in case a visitor is on something older. The <picture> element handles this for you: list the modern formats as <source type="image/avif"> and <source type="image/webp"> first, then a plain <img src="photo.jpg"> last. The browser picks the first format it understands and falls back to the JPEG if it understands none.

Related lessons