The previous page put content images in the HTML, where they have alternative text, intrinsic dimensions and a place in the document. This page covers the other half of the split from What an Image Costs: images that are presentation, written in the style sheet. It assumes the background properties from the CSS section's Properties page and goes further: the image functions, the properties other than background that take an image, the tools for fitting replaced content, and the costs that come with putting an image where the browser finds it late.
1. Content or presentation: where each image belongs
The test from page 01 still decides everything: if the image failed to load, would the reader miss something? If so, it is content, and it goes in the HTML. If the page would only look plainer, it is presentation, and CSS is usually the better home.
The reason is not tidiness. An image in CSS has a different set of properties from an image in the HTML, and they suit decoration and fail content:
<img> in the HTML | Image in CSS | |
|---|---|---|
| Alternative text | alt, read by screen readers and shown when the image fails | None. Assistive technology does not see background images at all |
| Discovery | Early: the browser's preload scanner sees it while the HTML is still arriving | Late: only after the CSS has loaded and a rendered element matches the rule |
| Printing | Prints | Background images do not print unless the reader turns on a browser setting |
| Reader modes, feeds, copy and paste, image search | Carried along with the content | Usually dropped |
| Changing with the design | Needs <picture> and markup changes | A media query or a custom property in the style sheet |
| Forced colors (high contrast) | Shown | Gradients are removed; url() images are kept but may lose their contrast |
Every row is a reason a photograph of the product, a chart or a diagram must not be a background image, and every row is also a reason a texture, a scrim or a decorative flourish should be one. A reader who prints a recipe wants the text, not the watercolor wash behind it. A screen reader user gains nothing from hearing about a gradient.
The Images deck described three methods for presentational images. Two are CSS: a plain background-image, and image-set(), which lets the style sheet offer densities and formats. The third is an <img> in the HTML with an empty alt="". That is legitimate when the decoration needs what only the HTML gives: early discovery, srcset with width descriptors, lazy loading, or a place in the document flow. The empty alt must be present. An <img> with no alt at all is treated as a content image whose description is missing, and many screen readers will read out the file name instead.
2. Background images and layers
The Properties page lists the eight background longhands, the shorthand and how layers stack. This section adds what matters when the background is a photograph rather than a color.
Sizing: cover and contain
A raster image has an intrinsic size and an intrinsic aspect ratio, and the box it decorates almost never matches either. background-size decides what gives way:
cover never leaves a gap and contain never crops; each pays for that with the other.For a photograph behind text, cover is almost always right, and it brings a responsibility: the crop changes with the box, so on a narrow phone a wide photograph loses most of its width. background-position chooses which part survives. A percentage aligns the same point of the image and the box, so center 30% keeps the subject that sits a third of the way down the photograph in view at every width.
contain suits a logo or an illustration that must be seen whole, usually with background-repeat: no-repeat, since a contained image smaller than its box will otherwise tile. Tiling is what repeat is for: a small texture or a pattern, with space and round to avoid cut-off tiles at the edges.
Positions and writing direction
The house style uses logical properties, so the natural wish is a logical position: a decorative image at the start of a heading, wherever "start" is in the reader's language. The CSS Backgrounds Level 4 draft defines keywords for that, such as x-start and inline-start, but in October 2026 only Safari 26 supports the x-start family, and Chrome and Firefox support none of them. They are not Baseline, so use physical keywords and flip them for right-to-left text with :dir(), which is Baseline:
Layers, a scrim, and a color underneath
The most useful background on most sites is two layers and a color: a semi-transparent gradient over a photograph, over a solid color. Each part has a job. The photograph sets the mood. The gradient, called a scrim, darkens the photograph so that light text keeps its contrast wherever it lands. The color shows while the photograph loads, if it fails, and if the reader has images turned off, and the text is still readable over it.
The demo below puts this and six other techniques on one page. Each example is plain HTML, and every image that is decoration comes from the style sheet.
background-attachment, cautiously
background-attachment: fixed pins the image to the viewport while the content scrolls over it. It is not Baseline: Safari on iOS recognizes fixed and ignores it, and in other browsers it makes the page repaint more as it scrolls. If the effect is the point of a design, the design will not survive on half the phones that load it. Treat it as an enhancement that can quietly fail. local, which scrolls the background with an element's own scrolling content, is useful for scroll shadows and is safer.
Background images can be the LCP
Largest Contentful Paint (LCP) is the time until the largest image or text block in the viewport is drawn, and it is the main measure of how fast a page feels. A background image loaded with url() counts as a candidate; a gradient does not. So a full-width hero photograph in CSS is usually the page's LCP element, and it is also the image the browser discovers last. Section 9 deals with that.
3. Gradients are images
In CSS a gradient is not a color. It is an <image> value, generated by the browser at the size of the box, so it goes anywhere an image goes: a background layer, a list marker, a border image, a mask. It costs no request and no bytes beyond the text of the rule, and it stays sharp at any size and density, which is more than any raster file can say.
| Function | Colors change | Typical use |
|---|---|---|
linear-gradient() | Along a line, at an angle or toward a side | Scrims, fades, subtle surface shading |
radial-gradient() | Outward from a point, as a circle or ellipse | Vignettes, spotlights, soft glows |
conic-gradient() | Around a point, like a clock hand | Pie charts, color wheels, angled patterns |
repeating-*-gradient() | The same, repeated along its line, radius or angle | Stripes, rings, hazard tape |
Two stops at the same position make a hard edge instead of a blend, which turns gradients into a drawing tool. A repeating linear gradient with hard stops is a stripe pattern; a conic gradient with hard stops is a pie chart. The interpolation can also be chosen: in oklch blends through a perceptually even color space and avoids the gray, muddy middle that a blend between two saturated colors gets in the default sRGB. Color spaces in gradients have been Baseline since 2024.
Gradients are where the "eliminate" step from page 01 pays off most often. A fade, a stripe, a soft glow or a tinted panel that once shipped as a PNG is now one line of CSS. Drawing whole illustrations in gradients is possible and is a fine puzzle, but past a few layers an SVG is easier to read and to change; SVG and Vector Images draws that line.
4. image-set(), with type() for formats
A background image written with url() is one file for every reader. The HTML has srcset and <picture> to offer choices; CSS has image-set(). It takes a list of images, each with a resolution (a density such as 1x or 2x, the same idea as an srcset pixel density descriptor) and optionally a type, the image's MIME type in a type() function. The browser chooses one and downloads only that one.
| File | type() | Bytes |
|---|---|---|
lake-800.avif | image/avif | 21,823 |
lake-800.webp | image/webp | 28,728 |
lake-800.jpg | image/jpeg | 33,646 |
The rule for choosing is short. The browser first drops every candidate whose type it cannot decode. Among candidates with the same resolution, only the first one listed is kept. From what remains, it picks the resolution that best suits the screen's device pixel ratio, and it may weigh other things too, such as a data-saver setting. So list formats from best to most compatible, as in <picture>, and the order does the work:
Combining both ideas means listing every format at every density, best format first. That is verbose, so most sites settle for one: formats for a background shown at a fixed size, or densities for a small, sharp-edged raster such as a texture.
Support has changed since the deck called image-set() with type() a Firefox-only feature in early 2023. Chrome dropped its old prefixed, URL-only version and shipped the full function in version 113, and Safari added type() in Safari 17. image-set() has been Baseline since September 2023 and Baseline widely available since March 2026. AVIF itself has been Baseline widely available since July 2026, so a list of AVIF then JPEG covers essentially every current browser.
Two limits remain. image-set() has no width descriptors and no sizes: it chooses by density, not by how wide the box is. To change a background with the layout, use media or container queries, as the deck's "Method 1" slide did. And a media query on resolution, the old way to send a sharper background to high-density screens, is now only a fallback:
5. List markers: list-style-image and ::marker
A list item's bullet is a marker box, and the Properties page covers its basic styling. Three ways exist to put a picture there, and they differ in what they can do.
list-style-image replaces the bullet with any image: a file, a data: URL, or a gradient. It is supported everywhere, and it is blunt. The image is drawn at its intrinsic size, with no property to size or nudge it, so an SVG marker needs a width and height in its own markup. And because a CSS image is a separate document, it cannot use the page's currentColor; its colors are fixed, including in dark mode. Pick a color that works on both backgrounds, and keep list-style-type as the fallback:
::marker selects the marker box itself. Only a short list of properties applies: color, the font properties, white-space, content, and animations and transitions. content is the interesting one, because it accepts a string, a counter or an image, so li::marker { content: url(pin.svg) " "; } would replace the bullet. The catch is Safari: its ::marker applies only color and font-size, which is why ::marker as a whole is not Baseline. Use it for color and font weight, which work everywhere, and do not rely on content there.
A string is the third option, and often the best: list-style-type: "▸ " uses those characters as the marker. Text takes the list's color, scales with its font, and adapts to dark mode for free.
Before these, marker images were faked with a background on each item. It still appears in old themes, and it is worth recognizing:
The fake bullet is lost when backgrounds are not printed or colors are forced, it sits at a fixed pixel offset that breaks as soon as the text size changes, and removing the real marker can remove the list semantics in Safari as well. A real marker keeps all of that.
6. content: url(), border-image and mask-image
Three more properties take an image. Each solves a problem that once needed an extra file or extra markup.
Generated images and their alt text
The content property of ::before and ::after can insert an image: content: url(icon.svg). Unlike a background, a generated image is something assistive technology may read, so it has a place for alternative text. A slash after the content introduces it:
The text after the slash is what a screen reader announces in place of the image or symbol, and an empty string marks it as decoration. The syntax is Baseline since July 2024, when Firefox 128 added it. A browser too old for the slash drops the whole declaration, so if the generated image is essential, declare a plain content line first as a fallback. Keep the habit from the Properties page, though: generated content is still presentation. Anything the reader must have belongs in the HTML.
border-image
border-image paints a border from an image instead of a style and color. With a raster file it slices the image into nine regions, four corners, four edges and a middle, and stretches or repeats the edges along the box: the old way to build a decorative frame. With a gradient it is much simpler, and it is the common use today:
The 1 is the slice: it tells the browser to use the whole gradient for every edge. The plain border line comes first because border-image draws in the space the border reserves, and because the border is what remains if the image is turned off. The limit to know: border-image ignores border-radius, so the corners stay square. For a rounded gradient border, layer two backgrounds with different background-clip values instead.
mask-image
A mask decides which parts of an element are visible. Where the mask image is opaque, the element shows; where it is transparent, the element is cut away; partial transparency fades it. Any image works as a mask: an SVG, a PNG with transparency, or a gradient.
The pattern this unlocks is the recolorable icon. An SVG drawn as a background keeps its own colors, but an SVG used as a mask over an element whose background is currentColor takes the color of the text around it, including hover states, dark mode and forced colors:
The second rule fades a photograph out toward its bottom edge with a gradient mask; the color in a mask gradient does not matter, only its opacity. Unprefixed mask-image and the mask shorthand have been Baseline since December 2023, when Chrome 120 dropped its need for the -webkit- prefix, and Baseline widely available since June 2026. Older tutorials that write every mask property twice, once with -webkit-, were right at the time; for current browsers the prefix is no longer needed.
7. Fitting replaced content: object-fit, object-position, aspect-ratio
Sometimes the image is content and belongs in the HTML, but the layout needs it in a box of a different shape: a grid of square thumbnails, a card with a 16:9 media area, a round avatar. An <img> is a replaced element, one whose content comes from outside the document, and CSS has a pair of properties that do for its content what background-size and background-position do for a background.
object-fit | What happens to a 3:4 photograph in a square box |
|---|---|
fill (initial) | Stretched to the box: the cat is squashed sideways |
contain | Scaled to fit whole, with empty bands at the sides |
cover | Scaled to fill, with the top and bottom cropped |
none | Drawn at its intrinsic size, cropped by the box |
scale-down | Whichever of none and contain gives the smaller image |
aspect-ratio: 1 makes the box square whatever its width, object-fit: cover fills it, and object-position chooses the crop. Its initial value is 50% 50%, the center; for portraits 50% 25% usually keeps a face in frame. The width and height attributes stay on the <img> regardless: they give the browser the image's own ratio before the file arrives, and the CSS ratio overrides it for layout. Fluid and responsive images explains how those attributes prevent layout shift; the Layout page puts these boxes in grids.
All three properties are Baseline widely available. object-fit also applies to <video>, which is how a background-style video fills a hero area without distortion.
8. Dark mode, print and forced colors
Because a presentational image lives in the style sheet, it can change with the reader's situation without a line of HTML changing. Three situations matter.
Dark mode
A scrim tuned for a light page can look washed out on a dark one, and a bright photograph behind a dark interface glares. A prefers-color-scheme query swaps the layer, or, better, a custom property used by the layer:
Since September 2026, light-dark() also accepts two images, light-dark(url(day.webp), url(night.webp)), and picks one by the element's color scheme. It is newly Baseline, so pair it with the media query version for readers on browsers from before that month. Only the image the reader needs is downloaded either way.
Browsers do not print background images and colors by default; Chrome, Firefox and Safari each have a print option, off unless the reader turns it on, that saves ink by leaving them out. That default is usually right, and it is one more reason content must not be a background. A print style sheet should plan for it: drop the scrim and the photograph, put the text back to dark on white, and keep any rule that the meaning depended on.
print-color-adjust: exact asks the browser to print an element's backgrounds even when the reader has not, and it is Baseline since May 2025, when Chrome 136 dropped its -webkit- prefix. Use it for the rare background that carries meaning, never to force a decorative photograph onto a reader's printer. Chrome and Safari do not print backgrounds of <body> itself even then.
Forced colors
Forced colors mode is the general name for Windows contrast themes and similar settings: the reader picks a small palette, and the browser replaces the page's colors with it. The CSS Color Adjustment specification says exactly what happens to images: background-image computes to none unless the value contains a url(). So gradients disappear, including scrims, while photographs and SVG files in url() remain, and the background color under them is replaced by the reader's background color.
The consequence is subtle. A hero with white text over a photograph and a dark scrim loses the scrim in forced colors, and the text color is forced too, so contrast over the photograph is no longer under the author's control. Chromium-based browsers draw an opaque plate behind text in this mode to keep it readable, but the safer plan is to let the reader's palette win:
The forced-colors media query is Baseline widely available. Mask icons, from section 6, behave well here, because their color comes from currentColor, which the forced palette sets. Images for People covers the reader settings that affect images in full, and DevTools can emulate both forced colors and print in its Rendering panel.
9. Preloading a background image, and why that fights the browser
An image in the HTML is found almost at once. Browsers run a preload scanner over the HTML as it streams in, and it starts requests for <img> sources long before the page is built. A background image has no such luck. The browser must download the style sheet, parse it, build the page, and find that a rendered element matches the rule with the url() in it. Only then does the request start.
For decoration that is fine, and even good: it is the "load when needed" step for free. For a hero photograph that is the page's LCP element, it is slow. The deck's answer still holds: tell the browser about the image early with a <link rel="preload"> in the <head>.
The as="image" attribute is required: it sets the request's priority and lets the browser match the preloaded response to the later request from the CSS. The type attribute makes a browser that cannot decode AVIF skip the preload instead of fetching a file it will never use. For a background that changes with the screen, imagesrcset and imagesizes carry the same values as an <img>'s srcset and sizes, and the media attribute limits a preload to a media query. imagesrcset and imagesizes have been Baseline widely available since June 2026.
Why it fights the browser
The deck put it plainly: with a preload, you start fighting the browser. The browser's own discovery is not a bug. It finds images in the order they are needed and weighs them against the CSS, fonts and scripts the page also needs. A preload overrides that judgment, and every way it can be wrong costs the reader:
- It competes. A high-priority image download shares the connection with the style sheet. If the CSS arrives later because of it, the whole page renders later, not just the hero.
- It can miss. The preload's URL must be exactly the file the CSS ends up choosing. Preload
lake-800.jpgwhileimage-set()pickslake-800.avifand the reader downloads both. Chrome warns in the console when a preloaded resource is not used within a few seconds of the page loading. - It goes stale. The preload is in the HTML and the image choice is in the CSS. When a redesign changes one and not the other, nothing fails visibly; the page simply gets slower.
- It defeats laziness. A preloaded image is fetched even if the rule that uses it never matches, such as a hero hidden on small screens.
Often the better fix is to stop hiding the image. If the hero photograph is the largest thing on the screen and the page is slow without it, it may be content after all. An <img> with fetchpriority="high", laid under the heading with object-fit: cover from section 7, is found by the preload scanner, can use srcset with width descriptors, and needs no preload at all. Keep the preload for a background image that truly must be CSS and truly is in the first view. Image Performance treats preloading, priority and LCP in depth.
10. Summary
| Concept | Key points |
|---|---|
| Where an image belongs | Content in the HTML, presentation in the CSS. CSS images have no alt text, are found late, do not print by default and are dropped by reader modes. A decorative <img> needs alt="". |
| Backgrounds and layers | cover fills and crops, contain fits and leaves bands; position chooses the crop. Logical position keywords are not Baseline, so flip with :dir(). Layer a scrim over the photograph and a color under both. fixed attachment is unreliable. |
| Gradients | Gradients are <image> values with no download: linear, radial, conic and repeating, with hard stops for patterns and in oklch for clean blends. |
image-set() | Baseline widely available. The browser drops unsupported type() values, keeps the first candidate at each density and picks by DPR. One file is downloaded. No width descriptors. |
| List markers | list-style-image works everywhere but cannot be sized or recolored. ::marker is safe for color and font; its content does not work in Safari. A string marker is often best. |
| Generated content, borders, masks | content: url(x.svg) / "label" gives generated images alt text. border-image takes gradients but ignores border-radius. Unprefixed mask-image is Baseline; a mask over currentColor makes icons that follow the text color. |
| Fitting replaced content | aspect-ratio shapes the box, object-fit fits the image into it, object-position chooses the crop. Keep width and height on the <img>. |
| Adapting | Swap layers for dark mode with a media query or light-dark(); plan for backgrounds not printing; in forced colors, gradients vanish and url() images stay. |
| Preloading | <link rel="preload" as="image"> fixes late discovery for a hero background, but competes with the CSS, can double-download and goes stale. Often an <img> with fetchpriority="high" is the better answer. |