CSS Images Module Level 4

Editor’s Draft,

More details about this document
This version:
https://drafts.csswg.org/css-images-4/
Latest published version:
https://www.w3.org/TR/css-images-4/
Previous Versions:
Feedback:
CSSWG Issues Repository
Tracker
Inline In Spec
Editors:
Tab Atkins Jr. (Google)
Elika J. Etemad / fantasai (Apple)
Lea Verou (Invited Expert)
Suggest an Edit for this Spec:
GitHub Editor
Delta Spec:
yes
Test Suite:
https://wpt.fyi/results/css/css-images/

Abstract

This module contains the features of CSS level 4 relating to the <image> type and replaced elements. It includes and extends the functionality of CSS level 2 [CSS2] and in the previous level of this specification [css-images-3]. The main extensions compared to "CSS Images Module Level 3" [css-images-3] are several additions to the <image> type, such as the image() notation, the element() notation, and conic gradients. This level is currently maintained as a diff spec over the level 3 module.

CSS is a language for describing the rendering of structured documents (such as HTML and XML) on screen, on paper, etc.

Status of this document

This is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-images” in the title, like this: “[css-images] …summary of comment…”. All issues and comments are archived. Alternately, feedback can be sent to the (archived) public mailing list www-style@w3.org.

This document is governed by the 18 August 2025 W3C Process Document.

1. Introduction

This section is not normative.

This module introduces additional ways of representing 2D images, for example as a URL with color fallback, as conic gradients, or as the rendering of another element in the document.

1.1. Value Definitions

This specification follows the CSS property definition conventions from [CSS2] using the value definition syntax from [CSS-VALUES-3]. Value types not defined in this specification are defined in CSS Values & Units [CSS-VALUES-3]. Combination with other CSS modules may expand the definitions of these value types.

In addition to the property-specific values listed in their definitions, all properties defined in this specification also accept the CSS-wide keywords as their property value. For readability they have not been repeated explicitly.

2. 2D Image Values: the <image> type

The <image> value type denotes a 2D image. It can be a url reference, image notation, or gradient notation. Its syntax is:

<image> = <url> | <image()> | <image-set()> | <cross-fade()> | <element()> | <gradient>

An <image> can be used in many CSS properties, including the background-image, list-style-image, cursor properties [CSS2] (where it replaces the <url> component in the property’s value).

In some cases an image is invalid, such as a <url> pointing to a resource that is not a valid image format or that has failed to load. An invalid image is rendered as a solid-color transparent image with no natural dimensions. However, invalid images can trigger error-handling clauses in some contexts. For example, an invalid image in list-style-image it is treated as none, allowing the list-style-type to render in its place. [CSS2]

While an image is loading, is a loading image. Loading images are not invalid images, but have similar behavior: they are rendered as a solid-color transparent image with no natural dimensions, and may trigger fallback rendering in contexts that offer it, but must not trigger loading of fallback resources. Alternately, if a loading image happens to be replacing an already-loaded image (for example due to changes in the document or style sheet) and the UA is tracking this information, it may continue to render the already-loaded image in place of the loading image.

Partially-loaded images (whose natural dimensions are known, but whose image data is not fully loaded) may be either treated as loading images or as loaded images rendered with partial data. For example, a UA may render an interlaced GIF in place as soon as its first pass of pixel data has loaded or even as soon as the image header (which contains sizing data) has parsed and refresh the rendering as more data loads; or it may wait until the entire image has loaded before using it.

A computed <image> value is the specified value with any <url>s, <color>s, and <length>s computed.

2.1. Image File Formats

At minimum, the UA must support the following image file formats when referenced from an <image> value, for all the properties in which using <image> is valid:

The UA may support other file formats as well.

2.2. Image References: the url() notation

Note: No change from [css-images-3].

2.3. Fetching External Images

To fetch an external image for a stylesheet, given a <url> url and a CSS declaration block declaration, fetch a style resource given url, with ruleOrDeclaration being declaration, destination "image", CORS mode "no-cors", and processResponse being the following steps given response res and null, failure or a byte stream byteStream: If byteStream is a byte stream, load the image from the byte stream.

2.4. Resolution/Type Negotiation: the image-set() notation

Delivering the most appropriate image resolution for a user’s device can be a difficult task. Ideally, images should be in the same resolution as the device they’re being viewed in, which can vary between users. However, other factors can factor into the decision of which image to send; for example, if the user is on a slow mobile connection, they may prefer to receive lower-res images rather than waiting for a large proper-res image to load. The image-set() function allows an author to ignore most of these issues, simply providing multiple resolutions of an image and letting the UA decide which is most appropriate in a given situation.

This solution assumes that resolution is a proxy for filesize, and therefore doesn’t appropriately handle multi-resolution sets of vector images, or mixing vector images with raster ones (e.g. for icons). For example, use a vector for high-res, pixel-optimized bitmap for low-res, and same vector again for low-bandwidth (because it’s much smaller, even though it’s higher resolution).

The syntax for image-set() is:

<image-set()> = image-set( <image-set-option># )
<image-set-option> = [ <image> | <string> ]
                     [ <resolution> || type(<string>) ]?

We should add "w" and "h" dimensions as a possibility to match the functionality of HTML’s picture.

Each <string> inside image-set() represents a <url>.

The image-set() function can not be nested inside of itself, either directly or indirectly (as an argument to another <image> type).

Each <image-set-option> defines a possible image for the image-set() function to represent, composed of three parts:

Tests

An image-set() function contains a list of one or more <image-set-option>s, and must select only one of them to determine what image it will represent:

  1. First, remove any <image-set-option>s from the list that specify an unknown or unsupported MIME type in their type() value.

  2. Second, remove any <image-set-option>s from the list that have the same <resolution> as a previous option in the list.

  3. If there are no <image-set-option> left at this point, the function represents an invalid image.

  4. Finally, among the remaining <image-set-option>s, make a UA-specific choice of which to load, based on whatever criteria deemed relevant (such as the resolution of the display, connection speed, etc).

  5. The image-set() function then represents the <image> of the chosen <image-set-option>.

UAs may change which <image-set-option> they wish to use for a given image-set() over the lifetime of the page, if the criteria used to determine which option to choose change significantly enough to make it worthwhile in the UA’s estimation.

This example shows how to use image-set() to provide an image in three versions: a "normal" version, a "high-res" version, and an extra-high resolution version for use in high-quality printing (as printers can have extremely high resolution):
background-image: image-set( "foo.png" 1x,
                             "foo-2x.png" 2x,
                             "foo-print.png" 600dpi );
This example shows use of the type() function to serve multiple versions of the same image in both new, higher-quality formats, and older, more widely-supported formats:
background-image: image-set( "foo.avif" type("image/avif"),
                             "foo.jpg" type("image/jpeg") );

Note that the AVIF image is given first; since both images have the same resolution (defaulting to 1x since it’s unspecified), the JPEG image, coming second, is automatically dropped in UAs that support AVIF images.

In older UAs, however, the AVIF image is ignored (because the UA knows it doesn’t support "image/avif" files), and so the JPEG is chosen instead.

Raster images can be mixed with vector images, or even CSS generated images.

For example, in this code snippet a high-resolution image with subtle details is used on screens that can do it justice, while an ordinary CSS linear-gradient() is used instead for low-resolution situations:

background-image: image-set( linear-gradient(cornflowerblue, white) 1x,
                             url("detailed-gradient.png") 3x );

2.5. Image Fallbacks and Annotations: the image() notation

The image() function allows an author to easily generate a solid-color image from any color. Its syntax is:

image() = image( <color> )

The image() function represents a solid-color image of the specified color with no natural dimensions.

For example, one can use this as a simple way to "tint" a background image, by overlaying a partially-transparent color over the top of the other image:
background-image: image(rgba(0, 0, 255, .5)), url("bg-image.png");

This is equivalent to a single color stop gradient:

background-image: linear-gradient(rgba(0, 0, 255, .5)), url("bg-image.png");

background-color does not work for this, as the solid color it generates always lies beneath all the background images.

Note: This function previously defined a number of additional behaviors, but those have been shifted to [css-images-5].

2.6. Combining images: the cross-fade() notation

When transitioning between images, CSS requires a way to explicitly refer to the intermediate image that is a combination of the start and end images. This is accomplished with the cross-fade() function, which indicates the two images to be combined and how far along in the transition the combination is.

Note: Authors can also use the cross-fade() function for many simple image manipulations, such as tinting an image with a solid color or highlighting a particular area of the page by combining an image with a radial gradient.

The syntax for cross-fade() is defined as:

cross-fade() = cross-fade( <cf-image># )
<cf-image> = [ <image> | <color> ] && <percentage [0,100]>?

The function represents an image generated by combining one or more images.

The <percentage> represents how much of each image is retained when it is blended with the other images. The <percentage> must be between 0% and 100% inclusive; any other value is invalid.

If any percentages are omitted, all the specified percentages are summed together and subtracted from 100%, the result is floored at 0%, then divided equally between all images with omitted percentages at computed-value time.

While this is not reflected in the computed value, when all the arguments’ percentages sum to greater than 100%, the sizing/painting details effectively rescale them so that they sum to exactly 100%.

On the other hand, when the sum is less than 100%, the sizing/painting details effectively act like there’s an additional transparent argument, with its percentage set to the remaining value necessary to make the sum equal 100%.

If a <color> is provided, it represents a solid-color image with “automatic” dimensions (it doesn’t participate in the sizing of the result image at all; see details in the sizing details below).

Tests

2.6.1. cross-fade() Sizing

The dimensions of the image represented by a cross-fade() are a weighted average of dimensions of the <image> arguments to the function; the <color> arguments have no effect. They are calculated as follows:

To determine the natural dimensions of a cross-fade():
  1. Normalize mix percentages from the function’s arguments, and let args and leftover be the result.

  2. If leftover is 100%, return no natural dimensions.

  3. Let images be an empty list.

  4. For each <cf-image> argument of the function’s arguments:

    1. If argument is not an <image>, or is an <image> with no natural dimensions, continue.

    2. Let item be a tuple consisting of a width, a height, and a percentage.

    3. Run the object size negotiation algorithm for the <image>, as appropriate for the context in which the cross-fade() appears, and set item’s width and height to the width and height of the resulting concrete object size.

    4. Set item’s percentage to the argument’s percentage.

  5. If images is empty, return no natural dimensions.

  6. Return a natural width and natural height that are weighted averages of the width and height of each item in images, according to their corresponding percentages.

    Note: The percentages might sum to a value less than 100%, so a naive weighted-averaging process might need to normalize them first.

2.6.2. cross-fade() Painting

The image represented by a cross-fade() is a weighted average of the input arguments to the function, calculated as follows:

To determine the appearance of a cross-fade():
  1. Normalize mix percentages from the function’s arguments, and let args and leftover be the result.

  2. Let images be an empty list.

  3. Let size be a tuple of width and height, initialized to the result of finding the concrete object size of the cross-fade() function (using the natural dimensions of a cross-fade()).

  4. For each argument of the cross-fade() function:

    1. Let item be a tuple consisting of an image and a percentage.

    2. If argument has an <image>, rescale it to size’s width and height and set item’s image to the result. Otherwise, argument has a <color>; set item’s image to a solid-color image of the <color>, with size’s dimensions.

    3. Set item’s percentage to the argument’s percentage.

  5. If leftover is greater than 0%, append a tuple to images consisting of a solid-color transparent-black image with size’s dimensions, and a percentage equal to leftover.

  6. Let final image be an image with size’s dimensions, and every pixel being the weighted linear average of the corresponding pixels of each item’s image in images, weighted according to the item’s percentage. (Average both the color channels and the alpha channel of the pixels.) For the purpose of this calculation, each pixel’s color must be in pre-multiplied sRGB.

    Details on the above operation

    This is applying an N-way Porter-Duff dissolve operation to the source images. Wikipedia defines dissolve as a stochastic operation, with the result pixels independently randomly chosen from the source images’ corresponding pixels according to their source images’ weights, but as pixels shrink to infinitely small, this converges to doing color-averaging in pre-multiplied color space.

    In particular, this means that `cross-fade(white 50%, transparent 50%)` will produce a partially-transparent solid white image. (Rather than a partially-transparent gray, which is what you’d get if you averaged the opaque white and transparent black pixels in non-premultiplied space.)

    As converting to pre-multiplied does entail some loss of precision, and graphics libraries may or may not support this operation natively, as per usual any method can be used so long as it achieves the specified effect.

    For example, one can instead rebalance the percentages according to the alphas of each pixel, then do the color-channel averages in non-premultiplied space. E.g., to render cross-fade(rgb(255 0 0 / 1) 40%, rgb(0 255 0 / .5) 20%, rgb(0 0 255 / 0) 40%), rebalancing the percentages according to the 1 / .5 / 0 alphas would produce 40% / 10% / 0% (which renormalizes to 80% / 20% / 0%), at which point you can average the raw color channel values and end up with an rgb(204 51 0 / .5) image. (Note that the alpha channel is still averaged using the original percentages, not the rebalanced ones.)

  7. Return final image.

2.6.3. Simplifying Complex cross-fade()

Per WG resolution, define a notion of "equality" for images, and combine "same" images at computed-value time, summing their percentages.

Per WG resolution, simplify directly-nested cross-fade() at computed-value time by just distributing the percentage and flattening; cross-fade(A 10%, cross-fade(B 30%, C 70%) 90%) becomes cross-fade(A 10%, B 27%, C 63%).

2.7. Using Elements as Images: the element() notation

The element() function allows an author to use an element in the document as an image. As the referenced element changes appearance, the image changes as well. This can be used, for example, to create live previews of the next/previous slide in a slideshow, or to reference a canvas element for a fancy generated gradient or even an animated background.

Note: The element() function only reproduces the appearance of the referenced element, not the actual content and its structure. Authors should only use this for decorative purposes, and must not use element() to reproduce an element with significant content across the page. Instead, just insert multiple copies of the element into the document.

The syntax for element() is:

element() = element( <id-selector> )

where <id-selector> is an ID selector [SELECT].

Do we need to be able to refer to elements in external documents (such as SVG paint servers)? Or is it enough to just use url() for this?

This name conflicts with a somewhat similar function in GCPM. This needs to be resolved somehow.

Want the ability to do "reflections" of an element, either as a background-image on the element or in a pseudo-element. This needs to be specially-handled to avoid triggering the cy