CSS The background-image Property

The background-image property places one or more images behind an element’s content. It sits on top of the element’s background-color but underneath any text or child elements, which is why it’s the go-to tool for hero banners, textured cards, decorative overlays, and gradient backdrops. Unlike an <img> element, a background image carries no semantic meaning and is invisible to screen readers and text selection, so it should only be used for purely decorative imagery, never for content the user needs to perceive or copy.

This lesson covers how background-image works under the hood, how to combine it with its sibling properties (background-size, background-position, background-repeat), how to layer multiple images and gradients in a single declaration, and the mistakes that trip up almost every beginner.

Overview / How it works

Every element has a background layer that is painted before the element’s border and content. By default that layer is transparent. Setting background-color fills it with a solid color; setting background-image paints one or more images on top of that color, aligned within the element’s background positioning area (by default, the padding box — the content plus padding, but not the border).

The browser’s rendering engine treats background-image as a comma-separated list of image layers. The first value in the list is painted on top; later values are painted progressively further back, with the element’s background-color always at the very bottom. This is why you can stack a semi-transparent gradient over a photo to darken it for readable text — the gradient is listed first, the photo second.

Each layer accepts one of several image value types:

  • url(...) — a raster image (JPEG, PNG, WebP, GIF) or a vector image (SVG) loaded from a path or URL.
  • a CSS gradient function such as linear-gradient(), radial-gradient(), or conic-gradient() — these are computed images generated entirely by CSS, no network request required.
  • none — the default; no image is painted for that layer.

By itself, background-image only says what to paint. Four companion properties control how it’s painted, and understanding all four together is what makes background images actually usable:

Property Purpose Common values
background-repeat Whether the image tiles to fill the area repeat (default), no-repeat, repeat-x, repeat-y, round, space
background-size How large the image is drawn auto (default, natural size), cover, contain, explicit lengths/percentages
background-position Where the image starts within its box center, top right, percentages, lengths
background-attachment Whether the image scrolls with content or stays fixed to the viewport scroll (default), fixed, local

Because the browser applies these as independent, comma-separated lists that line up positionally with the background-image list, you can give each image layer its own size, position, and repeat behavior in a single rule — the engine matches the first background-image entry with the first background-size entry, the second with the second, and so on, cycling shorter lists if needed.

Syntax

background-image: none | <image> [, <image>]*;

<image> = url("path/to/file.jpg")
        | linear-gradient(direction, color-stop, color-stop, ...)
        | radial-gradient(shape, color-stop, color-stop, ...)
  • none — no image is drawn for that layer; the element shows only its background-color.
  • url(…) — the path to an image file. Quotes around the path are optional per spec but strongly recommended, especially if the path contains spaces, parentheses, or special characters.
  • gradient functions — generated on the fly by the browser; no HTTP request, infinitely scalable, and can include transparency for overlay effects.
  • comma-separated list — multiple values stack the images, first-listed on top.

Examples

Example 1: A single image with a fallback color

.card {
  width: 320px;
  height: 180px;
  background-color: #f0f4f8;
  background-image: url("https://example.com/images/leaf-pattern.svg");
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

Result: The card renders as a 320-by-180 pixel box. If the SVG loads, a single leaf pattern is centered inside the box, scaled to fit without cropping. If the image fails to load (bad URL, offline, blocked), the box still shows its light blue-gray background-color instead of an empty white gap — this is the fallback-color pattern and it’s considered essential for any background image that isn’t purely decorative filler.

This demonstrates the core lesson: always pair background-image with a background-color fallback and be explicit about background-repeat and background-size rather than relying on the tiling default.

Example 2: Layering a gradient over a photo

.hero-banner {
  min-height: 320px;
  background-image:
    linear-gradient(to bottom, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.65)),
    url("https://example.com/images/mountains.jpg");
  background-repeat: no-repeat, no-repeat;
  background-position: center, center;
  background-size: cover, cover;
  color: white;
}

Result: A single box shows a mountain photograph stretched to fully cover the element (cropping as needed to avoid distortion or gaps), with a dark gradient wash painted on top that is lighter near the top and noticeably darker near the bottom. White text placed inside the element stays legible against the darkened lower portion, which is exactly why the gradient stop opacities were chosen that way.

Notice how background-repeat, background-position, and background-size each list two comma-separated values — one per image layer, in the same order as background-image. The gradient (first layer) and the photo (second layer) each get matched to the value in the same position.

Example 3: A fixed, full-bleed hero section

.parallax-hero {
  min-height: 60vh;
  background-image: url("https://example.com/images/skyline.jpg");
  background-repeat: no-repeat;
  background-position: center top;
  background-size: cover;
  background-attachment: fixed;
  background-color: #1b1f27;
}

Result: The section fills 60% of the viewport height with a city skyline photo, cropped to cover the full width and height without stretching, anchored so the top of the image is visible. As the page is scrolled, the photo appears to stay pinned to the viewport (a simple parallax effect) while the section’s content scrolls over it — a well-known effect but one to use sparingly, since background-attachment: fixed is disabled on many mobile browsers and forces the browser to repaint the whole image on every scroll frame on desktop, which can hurt scroll performance on large or busy pages.

How it works step by step

When the rendering engine paints an element’s background, it performs roughly these steps for each comma-separated layer, from first (top) to last (bottom):

  • 1. Resolve the image. For url(), this may mean issuing a network request (or reading from cache); for a gradient function, the browser computes the gradient mathematically — no request needed.
  • 2. Determine the positioning area. By default this is the padding box (content + padding), controlled by background-origin. The clipping boundary — how much of the painted image is visible — is controlled separately by background-clip, which defaults to the border box.
  • 3. Compute the rendered size. background-size: cover scales the image up (preserving aspect ratio) until it fully covers the positioning area, cropping overflow. contain scales it until the whole image fits inside, potentially leaving empty space. Explicit sizes (200px 100px, 50% auto) are applied directly.
  • 4. Compute the position. background-position anchors the (already-sized) image within the positioning area — center means the image’s own center aligns with the area’s center; percentages align proportional points on each.
  • 5. Apply repeat behavior. If the sized image is smaller than the positioning area and background-repeat isn’t no-repeat, the engine tiles copies of it outward from the anchor point.
  • 6. Composite. Once every layer is computed, the engine paints them back-to-front: last layer first, first layer last, with the element’s background-color painted underneath everything.

Common Mistakes

Mistake 1: Unquoted paths with special characters

.banner {
  background-image: url(my photo.jpg);
}

This is invalid CSS — the space inside the unquoted url() breaks the token into two, so the whole declaration is dropped by the parser. Always quote the URL, especially when a filename might contain spaces, parentheses, or commas:

.banner {
  background-image: url("my-photo.jpg");
}

Mistake 2: Distorting the image with two percentage sizes

.card {
  width: 400px;
  height: 150px;
  background-image: url("logo.png");
  background-size: 100% 100%;
  background-repeat: no-repeat;
}

This is syntactically valid, but it stretches the image independently on each axis to exactly fill the box, which distorts its aspect ratio whenever the box’s proportions don’t match the image’s. Beginners often reach for 100% 100% thinking it means “fill the box nicely,” when what they actually want is cover (fills the box, crops overflow, keeps proportions) or contain (fits entirely inside, keeps proportions, may letterbox):

.card {
  width: 400px;
  height: 150px;
  background-image: url("logo.png");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

Best Practices

  • Always set a background-color alongside background-image as a fallback for failed loads and for the brief moment before the image finishes downloading.
  • Be explicit about background-repeat; the default repeat tiling surprises far more people than it helps.
  • Prefer background-size: cover for full-bleed photographic sections and contain for logos or icons that must never crop.
  • Use gradients (linear-gradient(), radial-gradient()) layered over photos instead of separately-edited darkened image files — it keeps the effect adjustable in CSS and avoids an extra image download.
  • Avoid background-attachment: fixed on long pages or mobile-first designs; test scroll performance before shipping it.
  • Serve appropriately sized and compressed images (WebP/AVIF where possible) — a background image is still a full network request and counts toward page weight and Core Web Vitals.
  • Remember background images are decorative only; if the image conveys information, use an <img> with proper alt text instead.

Practice Exercises

  • Exercise 1: Create a .card class with a fixed width and height, a light gray background-color fallback, and a decorative SVG background image that is centered and never repeats or crops, regardless of the box’s proportions.
  • Exercise 2: Build a .hero section that layers a dark-to-transparent linear-gradient over a photograph so that white heading text placed near the top of the section stays readable, while the bottom of the photo remains fully visible.
  • Exercise 3: Take the distorted-logo example from Mistake 2 and rewrite it three ways — once with cover, once with contain, and once with an explicit pixel size — then describe in a sentence how each would look different for a 400×150 box holding a square 200×200 logo.

Summary

  • background-image paints one or more images behind an element’s content, above its background-color.
  • Multiple comma-separated images layer from first (top) to last (bottom), with background-color always at the very back.
  • Values can be url() references or CSS gradient functions, or none.
  • background-repeat, background-size, background-position, and background-attachment each accept matching comma-separated lists to control every layer independently.
  • background-size: cover crops to fill without distortion; contain fits entirely inside, possibly with empty space; explicit percentages on both axes can distort the image.
  • Always quote url() paths and always provide a background-color fallback.
  • Background images are purely decorative and invisible to assistive technology — use an <img> element for meaningful imagery instead.