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
src— The image URL. Can be relative (/images/photo.webp), absolute (https://example.com/photo.webp), or a data URI.alt— Alternative text for screen readers and broken image fallback. Required for accessibility. Write it as if you are describing the image to someone who cannot see it.widthandheight— Intrinsic dimensions in pixels. Browsers use these to calculate the aspect ratio before the image loads, which prevents layout shift (CLS). Always include them. For a deep dive on why this matters, see HTML image dimensions explained.loading="lazy"— Defers loading until the image is near the viewport. Saves bandwidth on long pages. Do NOT lazy-load your LCP image (the hero/above-fold image) — that delays the largest paint. See our lazy loading guide for the full strategy.decoding="async"— Tells the browser to decode the image off the main thread, reducing render-blocking.
When to use it
The basic <img> tag is correct when:
- You have a single image format and a single resolution
- The image does not need art direction (different crops for mobile vs desktop)
- You control the source image and know the exact dimensions
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.
Try it yourself
Reduce file size without visible quality loss — free, instant, no signup. Your images never leave your browser.
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
srcsetdeclares the available image files and their widths (thewdescriptor).sizestells the browser how wide the image will be rendered at each breakpoint.- The browser combines
sizeswith 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
- Always include
sizeswhen using width descriptors (w). Without it, the browser assumes100vw, which over-fetches on every layout where the image is not full-width. - Generate 3-5 size variants. More than that gives diminishing returns. A common set: 400, 800, 1200, 1600 pixels wide.
- Include
widthandheighton the<img>for CLS prevention. Use the dimensions of your largest source. - The
srcfallback is for browsers that do not supportsrcset(essentially none in 2026, but it costs nothing to include).
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
- Tiny UI icons under 1-2KB — the HTTP request overhead (DNS + TCP + TLS) exceeds the payload, so inlining is a net win.
- Email templates where external image references get blocked by mail clients.
- Single-file HTML exports that must be self-contained.
- Critical above-fold images that are very small (e.g., a blurred placeholder for progressive loading).
When base64 is a bad idea
- Any image over 2KB. Base64 encoding inflates file size by roughly 33% (3 bytes become 4 characters). A 50KB JPEG becomes a 67KB base64 string embedded in your HTML, which is not cached separately, not served from a CDN, and bloats every HTML response.
- Repeated images. An external image referenced on 50 pages is downloaded once and cached. A base64 image is re-downloaded with every HTML page.
- LCP images. Inlining a large hero image prevents the browser from starting the download during HTML parsing (preload scanner cannot find data URIs ahead of time).
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
- Decorative images that add visual flair but carry no informational content (texture overlays, ambient photography behind text).
- Images that need CSS-specific behaviors like
background-size: cover,background-blend-mode, or gradient overlays. - Images controlled by state — hover effects, active states, theme switching.
When NOT to use CSS backgrounds
- Content images. Search engines do not reliably index CSS background images. Screen readers skip them unless you add
role="img"andaria-label. If the image conveys information, use<img>or<picture>. - LCP candidates. CSS backgrounds are not discovered by the browser's preload scanner, which means they load later than
<img>tags. Using a CSS background for your hero image hurts LCP. - SEO-relevant images. Google Images indexes
<img>tags. It does not crawl CSS background images.
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:
altis for screen readers and is hidden from sighted users. It describes what the image shows.<figcaption>is visible text displayed below the image. It provides context — why the image matters, what the reader should notice, or the source.
A common mistake is making alt and <figcaption> identical. They should complement each other, not duplicate.
When to use <figure>
- Blog posts with captioned images
- Documentation with annotated screenshots
- Product pages with image descriptions
- Any context where the image needs a visible, associated caption
<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:
- Use
<img>or<picture>for hero images. The browser's preload scanner discovers these during HTML parsing, before CSS or JavaScript runs. CSS background images are invisible to the preload scanner. - Do not lazy-load the LCP image.
loading="lazy"defers the fetch, adding hundreds of milliseconds to LCP. Only lazy-load below-fold images. - Add a
<link rel="preload">hint for critical hero images when they are not in the initial HTML (e.g., loaded by a JavaScript framework):
<link
rel="preload"
as="image"
href="/images/hero.webp"
type="image/webp"
/>
- Serve modern formats. A 200KB JPEG replaced with an 80KB WebP loads faster purely from reduced bytes on the wire.
Cumulative Layout Shift (CLS)
Images without explicit dimensions cause layout shift when they load — the content below them jumps down. Prevention:
- Always set
widthandheighton<img>elements. The browser uses the aspect ratio to reserve space before download. Read more in HTML image dimensions explained. - Use
aspect-ratioin CSS for responsive images where width is percentage-based:
.responsive-img {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}
- CSS backgrounds need explicit container dimensions. Without
heighton the container element, a CSS background image causes no layout shift (nothing to shift) but also occupies no space until styled.
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
- Describe what the image shows, not what it is ("A bar chart showing WebP files are 35% smaller than JPEG" — not "chart.png").
- Keep it under 125 characters. Screen readers may truncate longer text.
- Skip "image of" or "photo of" — the screen reader already announces it as an image.
- For complex images (charts, infographics), provide a longer description via
aria-describedbypointing to a text block on the page.
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.
Try it yourself
Resize to exact dimensions for any platform — free, instant, no signup. Your images never leave your browser.