PLAINFINCH learn by building

Backgrounds and gradients

You will be able to add background colours, images and gradients, and keep text readable on top of a photo.

When is a plain background colour enough?

Most of the time. The background-color property fills the content and padding area, and the area under the border, with one colour. It is cheap to paint, never fails to load, and is the easiest place to check contrast with your text. Cards, banners, buttons and table stripes usually need nothing more.

The default value is transparent, so an element without a background shows its parent's background through it. That is why a <section> inside a white page looks white even though you never set a colour on it. It also means a background on body is visible behind every element that has no background of its own.

Always set a background-color on any element that also has a background image. The colour paints first and shows while the image downloads, and it stays if the image fails. If the text is white, pick a dark fallback so the text is readable during that gap.

.notice {
  background-color: #fff4d6;
  color: #4a3410;
}
FigureBackground layers stacked from top to bottom: the text, the first gradient layer, the url image, then background-color at the very bottom.Background layers stacked from top to bottom: the text, the first gradient layer, the url image, then background-color at the very bottom.
The first item in background-image is painted on top, the last just above background-color, and the element's text sits above them all. A tint layer listed first covers the photo listed after it.

How do you add a background image?

The background-image property takes a url() pointing at an image file. The path is resolved relative to the stylesheet, not the HTML page, which trips people up when the CSS lives in a css/ folder. Quotes around the URL are optional but help when it contains spaces or brackets.

A background image is decoration. Screen readers ignore it, it has no alt text, and it is not printed by default. If the picture carries meaning, such as a product photo or a chart, use an <img> in the HTML instead. Background images suit textures, hero photos behind a heading, and small decorative icons.

By default the image is drawn at its natural size, starting at the top-left corner of the padding box, and repeated in both directions to fill the element. A 600 by 400 photo in a 1000px wide header will tile, which is rarely what you want. The next three properties control that.

.hero {
  background-color: #6b4f2a;
  background-image: url("../img/loaves.jpg");
}

How do size, position and repeat work together?

The background-size property scales the image. cover scales it until it fills the whole element, cropping whatever spills over. contain scales it until the whole image fits inside, leaving gaps on two sides. You can also give a length or percentage, such as 64px 64px or 100% auto.

Use cover for photos behind content: the element is always filled, at the cost of cropping. Use contain for logos and illustrations where every part must stay visible. Combined with contain, add background-repeat: no-repeat or the image tiles into the gaps.

The background-position property decides which part stays visible when cover crops, or where a smaller image sits. Keywords such as center, top right and bottom work, as do lengths and percentages. center is a safe default for hero photos. If the subject of the photo is near the top, center top keeps it in view on narrow screens where cropping is heaviest.

The background-repeat property takes repeat, no-repeat, repeat-x, repeat-y, and two rarer values: space, which spreads whole tiles with gaps, and round, which stretches tiles so a whole number fit.

.logo-strip {
  background-image: url("logo.svg");
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
}

What does the background shorthand do?

The background shorthand sets colour, image, position, size, repeat and a few more in one line. Size goes right after position, separated by a slash: background: #6b4f2a url("loaves.jpg") center / cover no-repeat. The colour may only appear in the last layer.

Like every shorthand, background resets anything you leave out to its initial value. Writing background: #fff on a hover state wipes out the background image set elsewhere. When you only want to change the colour, use background-color. Reserve the shorthand for the first, complete declaration.

.hero {
  background: #6b4f2a url("loaves.jpg") center / cover no-repeat;
}

How do linear and radial gradients work?

A gradient is an image the browser draws, so it goes in background-image, never in background-color. A linear-gradient() blends colours along a straight line. The optional first argument sets the direction: to right, to bottom left, or an angle such as 135deg, followed by a comma. Without a direction it runs top to bottom.

The colours are called colour stops. Each can carry a position: linear-gradient(90deg, #f3d9a4 0%, #fdf6e3 60%) holds the first colour at the left edge and finishes the blend 60% of the way across. Two stops at the same position give a hard edge, which is how stripes and split backgrounds are made.

A radial-gradient() spreads outward from a centre point as a circle or ellipse. radial-gradient(circle at top left, #ffe9b8, transparent 60%) puts a soft warm glow in one corner. Radial gradients are good for spotlights and vignettes.

Gradients scale to the element with no download and no blur. A gradient between two very different hues can pass through a muddy grey in the middle. Adding in oklch after the direction, as in linear-gradient(to right in oklch, ...), interpolates in a perceptual colour space and keeps the middle bright. That syntax works in all current browsers since 2023.

.banner {
  background-image: linear-gradient(135deg, #f3d9a4, #e9a86a);
}

.split {
  background-image: linear-gradient(to right, #2f6b3c 50%, #fdf6e3 50%);
}

How do you layer several backgrounds?

The background-image property accepts a comma-separated list. Each item is a layer, and the first one listed is painted on top. The other background properties take matching lists: the first background-size value goes with the first image, and so on. If a list is shorter, the browser repeats it.

The colour is the bottom of the stack, under every image. That order lets you combine a texture, a gradient and a photo on one element with no extra markup. A common use is a small decorative icon over a gradient, with no-repeat on the icon and the gradient left to fill the box.

.card {
  background-image:
    url("wheat.svg"),
    linear-gradient(#fffaf0, #f4e6cc);
  background-size: 48px, auto;
  background-position: bottom 12px right 12px, center;
  background-repeat: no-repeat;
}

How do you keep text readable over a photo?

A photo has light and dark patches, and some of the text will land on the wrong one. The usual fix is a semi-transparent dark layer between the photo and the text. Put a gradient with the same colour at both ends first in the list, then the photo: linear-gradient(rgb(0 0 0 / 0.55), rgb(0 0 0 / 0.55)), url("loaves.jpg"). The photo still shows, dimmed, and white text gets steady contrast.

For a lighter touch, fade only where the text is: linear-gradient(to top, rgb(0 0 0 / 0.7), transparent 60%) darkens the bottom of the image under a caption and leaves the top clear. Check contrast at the worst spot with the DevTools colour picker, which reports the ratio against the colour under the cursor. Aim for at least 4.5 to 1 for body text.

Keep the dark background-color as well. While the image loads, the tint layer is still there, but the photo is not, and white text on the page colour would vanish.

What does background-clip do?

The background-clip property decides how far the background paints. The default, border-box, paints under the border, which shows through dashed or semi-transparent borders. padding-box stops at the inside edge of the border. content-box paints only behind the content.

The value text paints the background only inside the letter shapes. With color: transparent that gives gradient text. It is supported in all current browsers, and Safari now accepts it without the -webkit- prefix. Keep a solid color fallback in mind for readers using forced colours, and use it for headings only, because thin gradient body text is hard to read.

.title {
  background-image: linear-gradient(90deg, #b4541a, #2f6b3c);
  background-clip: text;
  color: transparent;
}

Field notes

  • A plain background colour is enough for most elements.
  • background-size: cover fills the box and crops whatever spills over.
  • Gradients are images, so they go in background-image.
  • With several layers, the first one listed sits on top.

Easy to get wrong

A gradient is an image. In background-color it is invalid and the declaration is dropped.

The shorthand resets the image, size and position too, so any icon or gradient on the button disappears on hover.

Without a dark fallback colour the white text is invisible until the photo loads, and stays invisible if it fails.

Questions people ask

Why does my background image not show?

Check the path first: url() is relative to the CSS file. Then check that the element has a height. An empty <div> is 0px tall, so there is nothing to paint on.

Should hero photos be background images or img elements?

If the photo is decoration behind a heading, a background is fine. If it carries content, or you want responsive srcset and lazy loading, use <img> with object-fit: cover and position the text over it.

Can I animate a gradient?

Gradients cannot transition directly. You can animate background-position on an oversized gradient, or animate a registered custom property used inside the gradient.

What does background-attachment: fixed do?

It pins the image to the viewport so content scrolls over it. It is ignored on iOS Safari and can make scrolling janky, so use it sparingly.

Keep going