albert.walickihire me
← all solutions
css

When to use img vs CSS background-image?

Use img for images that are content and CSS background-image for decoration. What each one gives you for accessibility, SEO, LCP and responsive images.

Ask whether the page would lose information without the image. If it would, the image is content, so use <img>. If it's decoration, like a texture, a pattern or a shape behind a heading, use background-image.

<!-- Content: a product photo -->
<img
  src="/products/chair-800.jpg"
  alt="Oak dining chair with a woven seat"
  width="800"
  height="800"
>
/* Decoration: a dotted pattern behind a section */
.newsletter {
  background-image: url('/patterns/dots.svg');
  background-repeat: repeat;
}

Product photos, avatars, article images, logos, charts and screenshots are content. Background textures, ornaments and blurred color shapes are decoration. When I'm not sure, I use <img>.

What img gives you

  • Alt text. Screen readers read the alt. A CSS background has no text alternative.
  • Image search. Search engines index <img> elements. CSS background images don't show up in image search.
  • Responsive images. srcset and sizes let the browser download a file that fits the screen.
  • Lazy loading with loading="lazy", no JavaScript needed.
  • Priority with fetchpriority="high" for the image that is your Largest Contentful Paint (LCP) element.
  • Early discovery. The browser's preload scanner finds <img> in the HTML before the CSS has loaded. A CSS background is only found after the stylesheet is downloaded and the browser has worked out which elements use it, which slows down LCP when that image is the hero.
  • Printing. Browsers don't print background images by default.
  • No layout shift. The width and height attributes let the browser reserve the space before the image loads.

A hero image as <img>:

<img
  src="/hero-1200.jpg"
  srcset="
    /hero-800.jpg 800w,
    /hero-1200.jpg 1200w,
    /hero-2000.jpg 2000w
  "
  sizes="100vw"
  width="2000"
  height="1000"
  alt="Designers sketching app screens around a table"
  fetchpriority="high"
>

Don't add loading="lazy" to the LCP image. Lazy loading is for images further down the page.

What background-image gives you

  • Layers. Stack gradients and images in one declaration, for example a dark overlay that keeps text readable.
  • Repeating patterns with background-repeat and background-size.
  • Different resolutions with image-set().
  • Decoration that stays out of the HTML, so screen readers never see it.
.promo {
  background-image:
    linear-gradient(rgb(0 0 0 / 0.6), rgb(0 0 0 / 0.2)),
    image-set(
      url('/shapes.png') 1x,
      url('/shapes@2x.png') 2x
    );
  background-size: cover;
}

"But I need cover"

background-size: cover used to be the main reason to put photos in CSS. <img> does the same with object-fit: cover, and you can still place text on top:

.hero {
  position: relative;
  isolation: isolate;
}

.hero img {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

isolation: isolate keeps the image behind the text but inside .hero. Use object-position to choose which part of the photo stays visible when it's cropped. There's more in how to fit an image into its container.

If an LCP image really has to be a CSS background, preload it so the browser finds it early:

<link rel="preload" as="image" href="/hero.jpg" fetchpriority="high">

Decorative images in HTML

Sometimes a decorative image ends up in the HTML anyway, for example from a CMS. Give it an empty alt="" so screen readers skip it. Don't leave alt out entirely, because some screen readers then read the file name.

The reverse doesn't work well: a content image in CSS patched with role="img" and aria-label on a <div> gets a text alternative, but it still misses everything else in the <img> list above.

more solutions
work with me

Got something that needs building?

Frontend builds, full-stack features in Django, design-system work. Available for work.

See my workGet in touch