← All articles 12 min read

Image to HTML: 6 Ways to Embed Images (With Code)

Adding an image to HTML sounds simple — slap an <img> tag in there and move on. And for a single decorative photo on a personal page, that works fine.

But the moment you care about performance, accessibility, responsive behavior, or Core Web Vitals scores, image embedding becomes a genuine engineering decision. The method you choose affects Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), bandwidth consumption, cacheability, and how screen readers interpret your content.

This article covers six distinct methods to get an image into HTML, when each one makes sense, and what trade-offs you accept with each approach. Every code example is copy-pasteable and production-ready.

Before embedding anything, make sure your images are properly compressed. An unoptimized 2MB hero image will tank your LCP regardless of which HTML method you use. Pixotter's compression tools run entirely in your browser and typically cut file sizes by 40-80% with no visible quality loss.


1. The Basic <img> Tag

The <img> element is the foundational way to embed an image in HTML. It has existed since HTML 2.0 (1995) and every browser on earth supports it.

<img
  src="/images/hero-photo.webp"
  alt="A golden retriever catching a frisbee in a park"
  width="1200"
  height="800"
  loading="lazy"
  decoding="async"
/>

What each attribute does

When to use it

The basic <img> tag is correct when:

For most images on most pages, this is the right choice. Do not over-engineer — if a plain <img> tag with width, height, loading, and alt meets your needs, ship it.


2. The <picture> Element for Format Fallbacks

The <picture> element wraps one or more <source> elements and a fallback <img>. The browser picks the first <source> it supports and ignores the rest.

<picture>
  <source srcset="/images/hero.avif" type="image/avif" />
  <source srcset="/images/hero.webp" type="image/webp" />
  <img
    src="/images/hero.jpg"
    alt="Mountain landscape at sunrise with fog in the valley"
    width="1600"
    height="900"
    loading="lazy"
    decoding="async"
  />
</picture>

This delivers AVIF to browsers that support it (Chrome 85+, Firefox 93+, Safari 16.4+), WebP to those that support WebP but not AVIF, and JPEG to everything else.

Why bother with multiple formats?

File size. An AVIF encode of a photographic image is typically 30-50% smaller than JPEG at equivalent visual quality. WebP sits between the two — about 25-35% smaller than JPEG. Over hundreds of images and thousands of page loads, these savings compound into measurably faster LCP and lower bandwidth costs.

If you need to generate WebP or AVIF versions of your existing images, Pixotter's format converter handles the conversion client-side — drop your images in and pick the output format.

For a detailed breakdown of when each format wins, read Best image format for web.

Art direction with <picture>

The <picture> element also handles art direction — serving a different crop on mobile versus desktop:

<picture>
  <source
    media="(max-width: 768px)"
    srcset="/images/hero-mobile.webp"
    type="image/webp"
  />
  <source
    media="(min-width: 769px)"
    srcset="/images/hero-desktop.webp"
    type="image/webp"
  />
  <img
    src="/images/hero-desktop.jpg"
    alt="Team photo — four engineers at a whiteboard"
    width="1600"
    height="900"
  />
</picture>

Art direction is for when you need a different image at different breakpoints — a tightly cropped portrait on mobile, a wide landscape on desktop. If you just need the same image at different sizes, use srcset on an <img> tag instead.


3. Responsive Images With srcset and sizes

The srcset attribute lets the browser choose the optimal resolution based on the device's viewport width and pixel density. This is the standard approach for responsive images that do not need art direction.

<img
  src="/images/product-800.webp"
  srcset="
    /images/product-400.webp   400w,
    /images/product-800.webp   800w,
    /images/product-1200.webp 1200w,
    /images/product-1600.webp 1600w
  "
  sizes="
    (max-width: 600px) 100vw,
    (max-width: 1200px) 50vw,
    33vw
  "
  alt="Wireless noise-cancelling headphones on a wooden desk"
  width="1600"
  height="1067"
  loading="lazy"
  decoding="async"
/>

How srcset and sizes work together

  1. srcset declares the available image files and their widths (the w descriptor).
  2. sizes tells the browser how wide the image will be rendered at each breakpoint.
  3. The browser combines sizes with the device pixel ratio (DPR) to calculate which file to fetch.

On a 375px-wide phone with 3x DPR where the image occupies 100vw, the browser calculates a needed width of 375 × 3 = 1125 pixels, and picks the 1200w source. On a 1440px desktop where the image occupies 33vw, the needed width is 1440 × 0.33 × 2 (assuming 2x DPR) ≈ 950 pixels, and the browser picks the 1200w source again.

Key rules

For guidance on choosing the right dimensions for your responsive breakpoints, see What image size for a website.


4. Base64 Inline Images

Base64 encoding embeds the image data directly in the HTML as a data URI. The browser decodes the string and renders the image without making a separate HTTP request.

<img
  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPj/HwADBwIAMCbHYQAAAABJRU5ErkJggg=="
  alt="1x1 transparent pixel placeholder"
  width="1"
  height="1"
/>

When base64 makes sense

When base64 is a bad idea

For a full guide on base64 encoding in multiple languages, see our image to base64 article.


5. CSS Background Images

CSS background-image places an image behind an element's content. It is a styling mechanism, not a content mechanism — that distinction matters for accessibility and SEO.

<div
  class="hero-banner"
  role="img"
  aria-label="Sunset over the Pacific Ocean with waves breaking on rocks"
></div>
.hero-banner {
  width: 100%;
  height: 500px;
  background-image: url('/images/hero-sunset.webp');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

Responsive CSS backgrounds

CSS background images support responsive behavior through media queries:

.hero-banner {
  background-image: url('/images/hero-mobile.webp');
}

@media (min-width: 768px) {
  .hero-banner {
    background-image: url('/images/hero-tablet.webp');
  }
}

@media (min-width: 1200px) {
  .hero-banner {
    background-image: url('/images/hero-desktop.webp');
  }
}

Modern CSS also supports the image-set() function for format negotiation (similar to <picture> in HTML):

.hero-banner {
  background-image: image-set(
    url('/images/hero.avif') type('image/avif'),
    url('/images/hero.webp') type('image/webp'),
    url('/images/hero.jpg') type('image/jpeg')
  );
}

Browser support for image-set() with type(): Chrome 113+, Firefox 89+, Safari 17+. Use @supports or a fallback background-image declaration for older browsers.

When to use CSS backgrounds

When NOT to use CSS backgrounds


6. The <figure> and <figcaption> Elements

<figure> wraps self-contained content — typically an image with a caption — and <figcaption> provides the visible caption text. This is semantic HTML: it tells browsers and screen readers that the caption describes the image.

<figure>
  <img
    src="/images/compression-comparison.webp"
    alt="Side-by-side comparison of JPEG at quality 80 vs WebP at quality 80, showing WebP is 35% smaller"
    width="1200"
    height="600"
    loading="lazy"
    decoding="async"
  />
  <figcaption>
    WebP at quality 80 produces a 35% smaller file than JPEG at the same
    quality setting, with no visible difference at normal viewing distance.
  </figcaption>
</figure>

alt vs <figcaption>

These serve different purposes and both should be present:

A common mistake is making alt and <figcaption> identical. They should complement each other, not duplicate.

When to use <figure>

<figure> is not required for every image. A decorative header image, a logo, or an inline icon does not need a caption and should not be wrapped in <figure>.


Image to HTML: Comparison Table

Method HTTP Requests SEO Indexed Screen Reader Support Responsive Caching Best For
<img> 1 per image Yes Yes (via alt) With srcset Yes (CDN, browser) Most content images
<picture> 1 (browser picks) Yes Yes (via alt) Yes (format + art direction) Yes Multiple formats, art direction
srcset/sizes 1 (browser picks) Yes Yes (via alt) Yes (resolution switching) Yes Same image, multiple resolutions
Base64 inline 0 (in HTML) Limited Yes (via alt) No No (embedded in HTML) Tiny icons under 2KB
CSS background 1 per image No Only with ARIA Via media queries Yes Decorative/ambient images
<figure> 1 per image Yes (enhanced) Yes (alt + caption) Inherits from inner <img> Yes Captioned content images

Performance Impact on Core Web Vitals

Your image embedding choice directly affects three Core Web Vitals metrics:

Largest Contentful Paint (LCP)

The LCP element is often an image. To minimize LCP time:

<link
  rel="preload"
  as="image"
  href="/images/hero.webp"
  type="image/webp"
/>

Cumulative Layout Shift (CLS)

Images without explicit dimensions cause layout shift when they load — the content below them jumps down. Prevention:

.responsive-img {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
}

Interaction to Next Paint (INP)

Large unoptimized images block the main thread during decoding. Use decoding="async" on <img> tags to move decode work off the main thread. For very large images (4K+ resolution), consider using content-visibility: auto on the containing element to skip rendering entirely until the element approaches the viewport.


Accessibility Checklist

Every image embedding method has accessibility requirements:

Method Requirement
<img> alt attribute is mandatory. Decorative images get alt="" (empty, not omitted).
<picture> alt goes on the inner <img>, not on <source>.
srcset Same as <img>alt on the <img> element.
Base64 alt on the <img> element, identical to any other <img>.
CSS background Add role="img" and aria-label to the container element.
<figure> alt on the <img> plus <figcaption> for visible caption. Do not duplicate text between them.

Writing good alt text


Frequently Asked Questions

What is the simplest way to add an image to HTML?

The <img> tag with src, alt, width, and height attributes. Four attributes, one self-closing element. Add loading="lazy" for below-fold images and decoding="async" for non-blocking decode. That covers 90% of use cases.

Should I use <picture> or srcset for responsive images?

Use srcset on an <img> when you need the same image at different resolutions (resolution switching). Use <picture> when you need different image formats (AVIF/WebP/JPEG fallback) or different crops at different breakpoints (art direction). They solve different problems — srcset is for size, <picture> is for format or composition.

Does base64 encoding hurt performance?

For images over 2KB, yes. Base64 inflates file size by 33%, prevents separate caching, bloats HTML payload size, and is invisible to the browser's preload scanner. For tiny icons under 1-2KB, the saved HTTP request outweighs the overhead. The break-even point depends on your HTTP version — with HTTP/2 multiplexing, the cost of additional requests is lower, pushing the threshold down to about 1KB.

Do search engines index CSS background images?

No. Google Images crawls <img> and <picture> elements but does not index images set via CSS background-image. If an image has SEO value — product photos, infographics, original illustrations — embed it with <img> or <picture>.

How do I prevent layout shift from images?

Always include width and height attributes on your <img> tags. The browser calculates the aspect ratio from these values and reserves the correct space before the image downloads. For responsive layouts, pair this with height: auto in CSS. See HTML image dimensions for a detailed walkthrough.

What image format should I use for HTML embedding?

WebP is the default choice for photographic and complex images — it has universal browser support and delivers 25-35% smaller files than JPEG. Use AVIF via <picture> with a WebP fallback for maximum compression. Use SVG for icons and logos. Use PNG only when you need pixel-perfect transparency or the image has very few colors. Our best image format guide covers every scenario.

Can I lazy-load a CSS background image?

Not natively. loading="lazy" only works on <img> and <iframe> elements. To lazy-load a CSS background, you need JavaScript — typically an Intersection Observer that adds a class with the background-image when the element enters the viewport. The complexity is one more reason to prefer <img> with loading="lazy" for content images.

How do I optimize images before embedding them in HTML?

Compress first, embed second. An unoptimized 3MB photo will be slow regardless of which HTML method you use. Run your images through Pixotter's compression tool to reduce file sizes by 40-80% with no visible quality loss — all processing happens in your browser, so your images never leave your device. Then use the format converter to generate WebP or AVIF versions for <picture> element fallback chains.


Putting It All Together

Here is a production-ready template that combines the best practices from every section — modern format delivery, responsive sizing, lazy loading, accessibility, CLS prevention, and semantic markup:

<!-- Hero image: above fold, not lazy-loaded, preloaded -->
<link
  rel="preload"
  as="image"
  href="/images/hero.avif"
  type="image/avif"
/>

<picture>
  <source
    srcset="
      /images/hero-800.avif   800w,
      /images/hero-1200.avif 1200w,
      /images/hero-1600.avif 1600w
    "
    sizes="100vw"
    type="image/avif"
  />
  <source
    srcset="
      /images/hero-800.webp   800w,
      /images/hero-1200.webp 1200w,
      /images/hero-1600.webp 1600w
    "
    sizes="100vw"
    type="image/webp"
  />
  <img
    src="/images/hero-1200.jpg"
    srcset="
      /images/hero-800.jpg   800w,
      /images/hero-1200.jpg 1200w,
      /images/hero-1600.jpg 1600w
    "
    sizes="100vw"
    alt="Pixotter dashboard showing a batch image compression workflow"
    width="1600"
    height="900"
    decoding="async"
    fetchpriority="high"
  />
</picture>

<!-- Content image: below fold, lazy-loaded, with caption -->
<figure>
  <picture>
    <source srcset="/images/comparison.avif" type="image/avif" />
    <source srcset="/images/comparison.webp" type="image/webp" />
    <img
      src="/images/comparison.jpg"
      alt="File size comparison: original JPEG 450KB vs WebP 180KB vs AVIF 120KB"
      width="1200"
      height="600"
      loading="lazy"
      decoding="async"
    />
  </picture>
  <figcaption>
    The same photograph compressed to three formats. AVIF delivers a 73%
    size reduction over the original JPEG with no visible quality loss.
  </figcaption>
</figure>

The pattern: <picture> for format negotiation, srcset/sizes for resolution switching, <figure> for captioned images, explicit width/height on every <img>, loading="lazy" only on below-fold images, and fetchpriority="high" on the LCP image.

Choose the simplest method that solves your specific problem. A basic <img> with four attributes is better than an over-engineered <picture> stack when you only serve one format at one size. Start simple, add complexity only when your performance data or your users demand it.