HTML Images and CSS Background Images

Use an <img> when the picture is content: a product photo, a chart, a portrait. Give it alt text, width and height. Use a CSS background-image when the picture is decoration behind text. Add srcset and sizes so phones download smaller files, and loading="lazy" for images below the first screen, never for the hero.

<img> or background-image?
Question<img>background-image
Does the picture carry meaning?Yes, with alt textNo, decoration only
Should search engines index it?YesRarely picked up
Responsive filessrcset and sizesimage-set() or media queries
Lazy loadingloading="lazy"Needs script
Crop to fill a boxobject-fit: coverbackground-size: cover
Printed by defaultYesUsually not

Updated

A decision table, a srcset builder that shows which file a phone or laptop would download, and editable examples of every pattern: alt text, fixed dimensions, lazy loading, a fast hero image, cropping with object-fit next to background-size, modern formats with <picture>, and responsive backgrounds with image-set().

srcset and sizes builder

Enter the widths you export, choose how wide the image is drawn, and see which file a device would download. Example output for 480, 960 and 1600 pixel files in a half-width layout:

<img src="/img/photo-960.jpg"
  srcset="/img/photo-480.jpg 480w, /img/photo-960.jpg 960w, /img/photo-1600.jpg 1600w"
  sizes="(min-width: 768px) 50vw, 100vw"
  alt="Describe the photo" width="1600" height="(your height)" loading="lazy">

A complete <img>

alt, width, height and lazy loading

The attributes give the real size; CSS makes it fluid. Because of width and height the box is reserved before the file loads. The demo images are small inline SVGs.

<style>img { max-width: 100%; height: auto; border-radius: 8px }</style>
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='300'%3E%3Crect width='600' height='300' fill='%230ea5e9'/%3E%3Ccircle cx='432' cy='105' r='54' fill='%23fde68a'/%3E%3Cpath d='M0 300 L210 135 L360 300Z' fill='%23065f46'/%3E%3Ctext x='8' y='20' font-size='14' fill='white' font-family='sans-serif'%3E600 x 300%3C/text%3E%3C/svg%3E"
  alt="Pine hills under a low sun at dusk"
  width="600" height="300" loading="lazy" decoding="async">

The hero image: eager and high priority

Use this on the one large image at the top of the page. Everything below the first screen gets loading="lazy" instead.

<img src="/img/hero-1600.jpg"
  srcset="/img/hero-800.jpg 800w, /img/hero-1600.jpg 1600w"
  sizes="100vw"
  alt="Chef plating a dish at the pass"
  width="1600" height="900"
  fetchpriority="high">

Alt text rules

Alt text by image type
Imagealt
Product photoDescribe the item and the detail that matters: Oak side table with two drawers
Chart or diagramState the takeaway: Sales doubled between March and June
Image that is a linkName the destination: Home
Decorative pattern or dividerEmpty: alt=""
Photo with the same caption beside itEmpty, or a short label, so it is not read twice
Text inside an imageThe same text

object-fit: cover next to background-size: cover

Both crop a picture to fill a box without stretching it. object-position and background-position choose which part stays in view.

The same crop, two ways

Left is an <img> with object-fit; right is a div with a background. They look the same; only the left one has alt text and lazy loading.

<style>
  .box { width: 46%; height: 150px; display: inline-block; border-radius: 8px; vertical-align: top }
  img.box { object-fit: cover; object-position: 70% 30% }
  div.box { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='300'%3E%3Crect width='600' height='300' fill='%230ea5e9'/%3E%3Ccircle cx='432' cy='105' r='54' fill='%23fde68a'/%3E%3Cpath d='M0 300 L210 135 L360 300Z' fill='%23065f46'/%3E%3Ctext x='8' y='20' font-size='14' fill='white' font-family='sans-serif'%3E600 x 300%3C/text%3E%3C/svg%3E") 70% 30% / cover no-repeat }
</style>
<img class="box" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='300'%3E%3Crect width='600' height='300' fill='%230ea5e9'/%3E%3Ccircle cx='432' cy='105' r='54' fill='%23fde68a'/%3E%3Cpath d='M0 300 L210 135 L360 300Z' fill='%23065f46'/%3E%3Ctext x='8' y='20' font-size='14' fill='white' font-family='sans-serif'%3E600 x 300%3C/text%3E%3C/svg%3E" alt="Pine hills under a low sun" width="600" height="300" loading="lazy">
<div class="box" role="presentation"></div>

CSS background images

A background behind text, with a fallback colour

The dark overlay gradient keeps the white text readable on any photo, and the background colour shows if the image fails.

<style>
  .banner {
    padding: 48px 20px; color: #fff; font: 700 24px system-ui; border-radius: 10px;
    background-color: #0c4a6e;
    background-image: linear-gradient(rgba(0,0,0,.45), rgba(0,0,0,.45)), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='300'%3E%3Crect width='600' height='300' fill='%230ea5e9'/%3E%3Ccircle cx='432' cy='105' r='54' fill='%23fde68a'/%3E%3Cpath d='M0 300 L210 135 L360 300Z' fill='%23065f46'/%3E%3Ctext x='8' y='20' font-size='14' fill='white' font-family='sans-serif'%3E600 x 300%3C/text%3E%3C/svg%3E");
    background-size: cover; background-position: center;
  }
</style>
<div class="banner">Spring walks start in April</div>

Responsive backgrounds with image-set()

image-set() lets the browser choose by pixel density or by file type. Keep a plain url() line first for older browsers.

.hero {
  background-image: url("/img/hero.jpg");
  background-image: image-set(
    url("/img/hero.avif") type("image/avif"),
    url("/img/hero.webp") type("image/webp"),
    url("/img/hero.jpg") type("image/jpeg")
  );
  background-size: cover;
}

@media (max-width: 640px) {
  .hero { background-image: url("/img/hero-small.jpg"); }
}

WebP and AVIF with <picture>

Modern formats with a JPEG fallback

The browser uses the first <source> whose type it supports. The <img> inside carries the alt text, the size and the fallback file, and is the element you style.

<picture>
  <source type="image/avif" srcset="/img/table-800.avif 800w, /img/table-1600.avif 1600w" sizes="(min-width: 768px) 50vw, 100vw">
  <source type="image/webp" srcset="/img/table-800.webp 800w, /img/table-1600.webp 1600w" sizes="(min-width: 768px) 50vw, 100vw">
  <img src="/img/table-800.jpg" srcset="/img/table-800.jpg 800w, /img/table-1600.jpg 1600w"
    sizes="(min-width: 768px) 50vw, 100vw"
    alt="Oak side table with two drawers" width="1600" height="1067" loading="lazy">
</picture>

Content or decoration

The question that settles most image decisions is whether a visitor would lose information if the picture failed to load. A team photo on an about page, a screenshot in a tutorial and a product shot in a shop all carry meaning, so they belong in the HTML with alt text.

A blurred texture behind a heading, a pattern in a footer or a photo that only sets the mood can live in CSS. That keeps the HTML clean and lets you swap the picture per screen size without touching markup. If text sits on top, make sure it still reads when the image is missing by setting a background colour too.

How the browser picks from srcset

With width descriptors the browser first reads sizes to learn how wide the image will be drawn, for example 100vw on phones and 50vw on wider screens. It multiplies that by the device pixel ratio and picks the smallest file that is at least that wide. Browsers may also pick a smaller file on a slow connection or a larger one already in cache.

Getting sizes right matters more than adding more widths. If sizes says 100vw but the image only fills a third of the screen on desktop, the browser downloads a file three times too wide.

Layout shift and the hero image

When an image without dimensions loads, everything below it jumps down. Width and height attributes let the browser compute the aspect ratio up front, which removes that jump and improves Cumulative Layout Shift.

The hero image is usually the Largest Contentful Paint element. Keep it eager, give it fetchpriority="high", serve it from your own domain or a fast image host, and avoid hiding it behind a script that sets it later.

How it works

  1. Start with the decision table to choose between an <img> and a CSS background.
  2. If you use <img>, enter your image widths in the srcset builder and pick the layout. Copy the generated tag.
  3. Move the screen-width and pixel-density sliders to see which file the browser would pick for each device.
  4. Copy any other pattern you need from the examples, all of which render live beside their code.

Tips

Frequently asked questions

Is background-image bad for SEO?

Search engines index images found in <img> elements far more reliably than ones set in CSS. If you want a picture to appear in image search or to describe your content, put it in an <img> with good alt text. Decorative backgrounds lose nothing by being CSS.

What should alt text say?

Say what the image shows that matters in this context, in a short sentence. For a product photo that might be the product and its colour; for a chart, the main finding. Skip phrases like image of. If the image is a link, describe where the link goes.

What is the difference between srcset with w and with x?

Width descriptors (480w, 960w) list each file's real width and let the browser choose using the sizes attribute and the screen's pixel density. Density descriptors (1x, 2x) suit images that are always shown at one fixed size, such as a logo.

Should I still use loading="lazy" if I use a framework?

Native lazy loading works in all current major browsers, so a plain attribute is enough for most images. Framework image components often add it for you; check that they do not also lazy-load the hero image.

Do I need both WebP and AVIF?

Not always. Both formats are supported by current major browsers. AVIF is often smaller for photos but slower to encode. A <picture> element with an AVIF source, a WebP source and a JPEG fallback covers everyone, but WebP alone with a JPEG fallback is a reasonable simpler choice.

Related: Image compressor, WebP converter, Alt text helper, Image cropper, HTML links guide.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap