Skip to content

Images, Media, and Embedding

beginner14 min read

The Fastest Way to Tank Your Core Web Vitals

A single unoptimized image can wreck your page's performance score. Here's what happens when you write this innocently looking HTML:

<img src="hero-photo.png">

No width. No height. No alt. No lazy loading. No responsive sizing. The browser has no idea how big this image is until it downloads it, so it reserves zero space — then jolts the entire layout when the image arrives. That's a CLS (Cumulative Layout Shift) spike. If the image is 5MB, it blocks other resources. If it's the largest visible element, it determines your LCP (Largest Contentful Paint) score.

One element. Five performance and accessibility problems.

Mental Model

Think of embedding media like hanging a painting on a wall. Before you drill, you need to know the dimensions (width and height), the mounting type (responsive or fixed), and what to show visitors who can't see it (alt text). If you just throw a painting at the wall without measuring, it falls down, damages the drywall, and looks terrible. Same thing happens to your layout when you drop in images without metadata.

The img Element Done Right

<img
  src="/images/hero.webp"
  alt="Developer working on a laptop in a modern office"
  width="1200"
  height="800"
  loading="lazy"
  decoding="async"
>

Let's break down every attribute:

  • src — the image URL. Use modern formats: WebP is ~30% smaller than JPEG, AVIF is ~50% smaller.
  • alt — alternative text. Describes the image for screen readers and displays when the image fails to load.
  • width and height — the intrinsic dimensions. The browser uses these to calculate the aspect ratio before downloading the image, preventing layout shift.
  • loading="lazy" — defers loading until the image is near the viewport. Saves bandwidth for below-the-fold images.
  • decoding="async" — tells the browser to decode the image off the main thread, preventing jank.
Quiz
Why should you always include width and height attributes on images?

Writing Good Alt Text

Alt text is not optional. It's required by WCAG 1.1.1 and it's one of the most important accessibility features on the web.

Guidelines:

<!-- Informative image: describe what it shows -->
<img src="chart.png" alt="Bar chart showing JavaScript as the most popular language at 65%, followed by Python at 48%">

<!-- Decorative image: empty alt (not missing alt) -->
<img src="decorative-wave.svg" alt="">

<!-- Image that's also a link: describe the destination -->
<a href="/profile">
  <img src="avatar.jpg" alt="Your profile">
</a>

<!-- Complex image: brief alt + longer description -->
<figure>
  <img src="architecture.png" alt="System architecture diagram showing three layers">
  <figcaption>The frontend communicates with the API gateway, which routes to microservices. Each service has its own database.</figcaption>
</figure>

Rules of thumb:

  • Never start with "Image of" or "Photo of" — screen readers already announce it as an image.
  • Be concise but specific — "Dog" is too vague. "Golden retriever catching a frisbee in a park" is good.
  • Decorative images get empty alt (alt=""), not missing alt. Missing alt makes the screen reader read the filename.
  • If the image contains text, include that text in the alt.
Quiz
What should you use for a purely decorative image's alt attribute?

Responsive Images

A single image doesn't work for all screen sizes. A 2400px hero image is overkill on a phone and wastes bandwidth. HTML gives you two tools for this:

srcset and sizes

<img
  src="hero-800.webp"
  srcset="
    hero-400.webp 400w,
    hero-800.webp 800w,
    hero-1200.webp 1200w,
    hero-2400.webp 2400w
  "
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 1200px"
  alt="Mountain landscape at sunset"
  width="2400"
  height="1600"
>
  • srcset — lists available image files with their widths (the w descriptor)
  • sizes — tells the browser how wide the image will display at each breakpoint
  • The browser combines srcset + sizes + device pixel ratio to pick the optimal file

The picture Element

Use picture when you need different image formats or different crops:

<!-- Format switching: modern browsers get AVIF, fallback to WebP, then JPEG -->
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Mountain landscape" width="1200" height="800">
</picture>

<!-- Art direction: different crops for different screens -->
<picture>
  <source media="(max-width: 600px)" srcset="hero-mobile.webp">
  <source media="(max-width: 1200px)" srcset="hero-tablet.webp">
  <img src="hero-desktop.webp" alt="Team working together" width="1200" height="800">
</picture>

The img inside picture is the fallback. It's also where you put alt, width, height, loading, and decoding — those attributes go on the img, not on picture or source.

Quiz
When should you use the picture element instead of just img with srcset?

Video and Audio

<!-- Video with multiple formats and accessibility features -->
<video
  width="720"
  height="480"
  controls
  preload="metadata"
  poster="video-thumbnail.jpg"
>
  <source src="tutorial.webm" type="video/webm">
  <source src="tutorial.mp4" type="video/mp4">
  <track kind="captions" src="captions-en.vtt" srclang="en" label="English" default>
  <p>Your browser doesn't support HTML video.
    <a href="tutorial.mp4">Download the video</a>.
  </p>
</video>

<!-- Audio -->
<audio controls preload="metadata">
  <source src="podcast.ogg" type="audio/ogg">
  <source src="podcast.mp3" type="audio/mpeg">
  <p>Your browser doesn't support HTML audio.
    <a href="podcast.mp3">Download the episode</a>.
  </p>
</audio>

Key attributes:

  • controls — shows play/pause, volume, progress bar. Without it, users can't interact with the media.
  • preload="metadata" — downloads just enough to get duration and dimensions. Better than preload="auto" (downloads the whole file).
  • poster — thumbnail image shown before the video plays.
  • track — captions, subtitles, or descriptions for accessibility.
Common Trap

Never set videos to autoplay with sound. Browsers actively block this (Chrome, Safari, Firefox all require muted autoplay). If you need autoplay (for a background video), use autoplay muted loop playsinline. But consider: autoplaying video is a significant performance cost and may cause vestibular discomfort for some users.

Embedding External Content

Iframes

<iframe
  src="https://www.youtube.com/embed/dQw4w9WgXcQ"
  width="560"
  height="315"
  title="Video: Rick Astley - Never Gonna Give You Up"
  loading="lazy"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
  allowfullscreen
></iframe>

Always include title on iframes. Screen readers announce the title to describe the embedded content. Without it, users hear "iframe" with no context.

The allow attribute controls which browser features the embedded page can use (camera, microphone, autoplay, etc.). The sandbox attribute goes further, restricting scripts, forms, and navigation:

<iframe
  src="https://untrusted-content.com/widget"
  sandbox="allow-scripts allow-same-origin"
  title="Third-party widget"
></iframe>
Quiz
Why should you always include a title attribute on iframe elements?

Production Scenario: Optimized Hero Image

Here's how to build a hero image that performs well across all devices:

<picture>
  <source
    srcset="hero-400.avif 400w, hero-800.avif 800w, hero-1600.avif 1600w"
    sizes="100vw"
    type="image/avif"
  >
  <source
    srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1600.webp 1600w"
    sizes="100vw"
    type="image/webp"
  >
  <img
    src="hero-800.jpg"
    srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1600.jpg 1600w"
    sizes="100vw"
    alt="Panoramic view of a code editor with a gradient background"
    width="1600"
    height="900"
    decoding="async"
    fetchpriority="high"
  >
</picture>

Notice:

  • No loading="lazy" on the hero image — it's above the fold, so we want it immediately.
  • fetchpriority="high" — tells the browser this is the most important image on the page (likely the LCP element).
  • Three formats: AVIF (smallest), WebP (good support), JPEG (universal fallback).
  • Three sizes per format: mobile (400w), tablet (800w), desktop (1600w).
Execution Trace
Parse picture
Browser evaluates source elements top to bottom
First matching source wins based on type support and media query
Check AVIF
Browser checks if it supports image/avif
Chrome, Firefox, Safari 16+ support AVIF
Evaluate sizes
Browser calculates display width: 100vw
On a 375px phone with 2x DPR, it needs a 750w image
Select file
Browser picks hero-800.avif (closest to 750w)
The browser picks the smallest file that covers the needed width
Render
Image displays with reserved 16:9 aspect ratio
Width and height prevent layout shift even before the image loads
What developers doWhat they should do
Omitting width and height on images
Without dimensions, the browser can't reserve space. When the image loads, everything shifts — causing CLS problems
Always include width and height to prevent layout shift
Using loading='lazy' on above-the-fold images
Lazy loading delays the LCP image, making the page feel slower. Above-the-fold images should load immediately
Only lazy-load images below the fold. Use fetchpriority='high' for the hero image
Serving only JPEG/PNG without modern formats
AVIF is ~50% smaller than JPEG at similar quality. WebP is ~30% smaller. Smaller files mean faster page loads
Use picture with AVIF and WebP sources, JPEG as fallback
Missing alt attribute on images
Screen readers read the filename if alt is missing. An empty alt (alt='') correctly tells screen readers to skip decorative images
Every image needs alt: descriptive for content images, empty string for decorative images

Challenge: Build a Responsive Media Section

Create an HTML section that includes: a responsive image with three sizes and two formats, a video with captions, and a third-party embed. Make everything accessible and performant.

Show Answer
<section>
  <h2>Course Preview</h2>

  <figure>
    <picture>
      <source
        srcset="preview-400.avif 400w, preview-800.avif 800w, preview-1200.avif 1200w"
        sizes="(max-width: 600px) 100vw, 800px"
        type="image/avif"
      >
      <source
        srcset="preview-400.webp 400w, preview-800.webp 800w, preview-1200.webp 1200w"
        sizes="(max-width: 600px) 100vw, 800px"
        type="image/webp"
      >
      <img
        src="preview-800.jpg"
        srcset="preview-400.jpg 400w, preview-800.jpg 800w, preview-1200.jpg 1200w"
        sizes="(max-width: 600px) 100vw, 800px"
        alt="Course dashboard showing progress through HTML modules"
        width="1200"
        height="675"
        loading="lazy"
        decoding="async"
      >
    </picture>
    <figcaption>The course dashboard tracks your progress through each module.</figcaption>
  </figure>

  <video width="800" height="450" controls preload="metadata" poster="video-poster.jpg">
    <source src="intro.webm" type="video/webm">
    <source src="intro.mp4" type="video/mp4">
    <track kind="captions" src="intro-en.vtt" srclang="en" label="English" default>
    <p>Your browser doesn't support video. <a href="intro.mp4">Download it here</a>.</p>
  </video>

  <iframe
    src="https://codepen.io/team/embed/preview/example"
    width="800"
    height="400"
    title="Live code example: CSS Grid layout"
    loading="lazy"
    sandbox="allow-scripts allow-same-origin"
  ></iframe>
</section>

Key accessibility and performance points:

  • Image uses figure/figcaption for visible caption
  • Image has three sizes across two modern formats plus JPEG fallback
  • loading="lazy" only on below-the-fold content (not the hero)
  • Video has captions track and a text fallback with download link
  • Iframe has a descriptive title and sandbox for security
  • All media elements have explicit dimensions to prevent layout shift
Key Rules
  1. 1Always include width, height, and alt on every img element — dimensions prevent layout shift, alt provides accessibility
  2. 2Use loading='lazy' for below-the-fold images, fetchpriority='high' for the hero/LCP image
  3. 3Serve modern image formats (AVIF, WebP) with JPEG fallback using the picture element
  4. 4Videos need controls, captions (track element), and a text fallback for browsers that don't support video
  5. 5Always include title on iframe elements — screen readers depend on it to describe embedded content