Image Preview lets a page provide a lightweight preview for an HTML image. The browser shows the preview while the final image loads, then replaces it directly with the final image. This moves common preview decoding, lifecycle, and replacement behavior from site-specific code into the browser.
Authors set the preview with a new previewsrc attribute on <img>. Existing src, srcset, and sizes behavior continues to select the final image.
<img
previewsrc="photo-preview.avif"
src="photo.avif"
width="1200"
height="800"
alt="A mountain reflected in a lake">
When an image is slow to load, the user can see an empty region without enough information to understand what will appear there. This is common on slow connections and image-heavy pages.
Sites can show previews today, but each site must build its own solution. Current approaches use a CSS background on the final image, replace the src of one image, or stack a decoded canvas with the final image. The site must also manage decoding, replacement, source changes, and any transition.
Example scenario: A user opens a photo gallery on a slow connection. The page knows a small preview for each photo. With Image Preview, the browser can show those previews in the image elements and replace each one as its final image becomes ready.
The table below describes the major patterns used by current implementations. Representative examples are provided in the appendix.
| Pattern | Format | How it works | Examples and evidence |
|---|---|---|---|
| 1. CSS background preview with final image overlay | Base64 8×8 BMP; tiny JPEG embedded in SVG; BlurHash-derived WebP; ThumbHash-derived PNG | Set the preview as the CSS background of the final <img> or its wrapper. The final image paints over it or becomes opaque after loading. |
Unsplash; Next.js <Image>; Jesus Film with Next.js; Tommy Chow homepage gallery preview |
| 2. Decoded compact hash canvas | BlurHash decoded to canvas pixels | Decode the hash in script and paint it into a canvas occupying the image's display area. Load the final <img> independently; after it loads or decodes, reveal it and hide or remove the canvas. |
Minds; Mastodon; Misskey; Nextcloud Talk; Jellyfin Vue |
| 3. Single-image source replacement | Cloudinary-transformed raster image; Cloudinary selects the delivered format automatically | Assign the placeholder URL to one <img>, preload the final resource, and replace the same element's src. |
Cloudinary training tool; Cico Jazz hero; Cloudinary plugin source |
| 4. Progressive stacked preview layers | BlurHash, small thumbnail, and larger preview | Stack progressively better representations and fade in each layer after it loads. | Nextcloud Photos |
<img> for both the preview and final image.src, srcset, and sizes.previewsrc API.loading, decoding, or fetchpriority.<image> values directly through previewsrc.<img>.Add a URL-valued previewsrc attribute to <img>.
The preview is temporary visual content for the image. It is painted in the same image element. The final resource continues to come from the existing image source attributes.
Authors should provide a preview that represents the same content as the final image. The browser cannot verify that correspondence, but unrelated or misleading preview content can give users an incorrect understanding of the image while the final resource loads.
The proposal is divided along specification and implementation boundaries:
| Capability | Scope | Intended venue |
|---|---|---|
previewsrc and previewSrc |
Core | WHATWG HTML |
| Preview fetching, decoding, display, replacement, and cancellation | Core | WHATWG HTML |
| Compact encodings such as BlurHash or ThumbHash | Independent image formats | Their respective format specifications and registrations |
| Author-customizable handoff | Optional extension | To be determined; potentially CSS View Transitions |
| Script-visible preview or handoff state | Deferred pending demonstrated use cases | To be determined |
The core proposal applies only to HTML <img>. It does not add attributes to <source>, <input type="image">, SVG <image>, <object>, or <embed>.
An <img> inside <picture> can use previewsrc, but the preview belongs to the <img> rather than to an individual <source>. The existing <picture>, srcset, and sizes algorithms continue selecting the final image. Version 1 deliberately provides one non-responsive preview URL and does not define previewsrcset or previewsizes.
The preview should therefore represent every final candidate that the <picture> or srcset can select. If art direction, localization, writing direction, color scheme, or another condition changes the image's subject materially, the author should use a neutral preview suitable for every candidate or omit previewsrc. Responsive preview selection can be considered as a later extension if implementation experience demonstrates that one preview is insufficient.
An author can use a small, low-resolution JPEG, WebP, or AVIF image as the preview:
<img
previewsrc="/images/forest-32.avif"
src="/images/forest-1600.avif"
srcset="/images/forest-800.avif 800w,
/images/forest-1600.avif 1600w"
sizes="(max-width: 700px) 100vw, 700px"
width="1600"
height="1067"
loading="lazy"
decoding="async"
alt="Forest trail in autumn">
The browser uses previewsrc for the preview. It uses the existing responsive-image algorithm to select the final image from srcset and sizes.
The preview can also be an inline data URL:
<img
previewsrc="data:image/avif;base64,..."
src="/images/city.avif"
width="1600"
height="900"
alt="City skyline at dusk">
Inlining avoids a separate preview request, but increases the size of the containing document.
previewsrc is URL-valued and cannot directly use CSS-generated images such as gradients, cross-fade(), Paint Worklets, or future mesh and patch gradients. Direct support would require a separate CSS integration.
previewsrc is a URL-valued content attribute reflected by the previewSrc USVString property using the same URL-reflection behavior as src. Reading previewSrc returns the resolved absolute URL, while getAttribute("previewsrc") returns the serialized attribute value. A missing or empty previewsrc means that the element has no preview. Removing or emptying the attribute makes pending preview work obsolete and allows the browser to cancel it.
Preview processing is integrated into HTML's existing update the image data processing model:
src, srcset, <picture>, and sizes, and creates or updates the final-image request without waiting for the preview.previewsrc, it resolves the preview URL against the document base URL and may create a preview request.If an update leaves the element without a valid final-image candidate, the browser does not process previewsrc. Pending preview work becomes obsolete, and a displayed preview is removed; the element then follows its ordinary no-source or broken-image rendering behavior. A preview cannot serve as a standalone image or fallback source.
When previewsrc resolves to the same URL as the selected final image, the browser may reuse the same fetch and decode rather than perform duplicate work. The resource is treated as the final image and does not participate in the preview lifecycle.
Final-image discovery, selection, and request creation must not wait for preview fetching or decoding. The browser schedules best-effort preview work so that it does not block those final-image operations and may choose its network, decoding, or task priority accordingly. An additional request can still consume bandwidth or contend with the final image.
The element's fetchpriority and decoding attributes continue to describe the final image. Version 1 adds no preview-specific priority control: preview decoding is asynchronous and best-effort. The browser may skip the preview when the final image is already available or when its scheduling policy determines that the preview is unlikely to be useful before the final image.
The preview is fetched with destination image and follows the same URL parsing, CORS, credentials, referrer-policy, Content Security Policy img-src, mixed-content, cookie, HTTP-cache, network-partitioning, and service-worker rules as other <img> requests. A separately fetched preview produces a Resource Timing entry under the rules for image requests, with initiatorType equal to "img". The preview URL is not exposed through currentSrc.
In this explainer, ready to paint means that a successfully fetched and decoded image representation is available for the browser to render. It does not mean that a rendering update has already painted the pixels, and it does not introduce a new script-visible state. A specification would express this condition using the existing HTML image-request and decoding states.
The proposed end-to-end flow is:
flowchart LR
A[Preview and final requests active] -->|Preview ready first| D[Preview displayed]
A -->|Final ready first| F[Final image displayed]
D -->|Final image ready| F
A -->|Preview unavailable| G[No preview displayed]
G -->|Final image ready| F
The numbered steps below are the text alternative for the diagram:
<img>.loading="lazy" causes the browser to defer the final-image request, it must also defer the preview request. When the browser begins loading the final image, it may begin best-effort preview processing according to the scheduling rules above.image-animation value on the <img> element applies to the preview as well as the final image. Replacing the preview ends its playback. This proposal adds no preview-specific animation controls.<img> state. Discarding the document makes preview work obsolete.Script can update the reflected preview property and the final source, as in a dynamic image gallery:
const image = document.querySelector("#gallery-image");
image.previewSrc = photo.previewUrl;
image.src = photo.url;
Changes to previewsrc, src, srcset, sizes, and relevant <source> elements participate in HTML's existing image-data update processing. For explanatory purposes, this document calls the preview and final-image work associated with one such update an image generation; it does not introduce a script-visible generation object.
Results from older generations are ignored and cannot newly replace painted content. The browser cancels obsolete requests, decoding, and optional handoffs when possible, including when a newer generation supersedes them, the final image becomes ready first, or the document is discarded. Previously painted content may remain until the current generation has a preview or final image ready.
The browser temporarily displays the preview inside the image element, but the selected final image remains the element's current image resource. Standard HTMLImageElement state and events, including currentSrc, complete, naturalWidth, naturalHeight, decode(), load, and error, continue to describe the selected final image.
The preview does not contribute intrinsic dimensions or otherwise determine the <img> element's layout size. The browser lays out the element using the same CSS, width, height, aspect-ratio, and final-image intrinsic-size rules that apply when previewsrc is absent.
If those rules establish a nonzero content box before the final image is ready, the browser scales and positions the preview within that box according to object-fit and object-position. Preview dimensions do not create or resize the box.
If the element has no nonzero content box, the browser may fetch and decode the preview but cannot visibly paint it. If a nonzero content box is later established while the final image remains pending, a ready preview may be painted according to the normal lifecycle. When the final image's intrinsic dimensions become available, they may affect layout under the existing <img> sizing rules. Authors should provide width and height, aspect-ratio, or CSS sizing when they want to reserve space before the final image loads.
The preview is not exposed through drawImage(), createImageBitmap(), or other image-extraction APIs. While only the preview is displayed, those APIs behave exactly as they do when the final image has no available image data; they do not use preview pixels. Once the final image is available, they use it under their existing algorithms.
Context menus, drag operations, saving, accessibility, and other semantics continue to identify the final image and its src/currentSrc, not the preview.
The preview lifecycle and lifecycle decisions define the rendering outcomes for pending, ready, unavailable, and failed resources.
In a browser that does not implement Image Preview, the unrecognized previewsrc attribute has no effect and the existing final-image attributes continue to work.
Script can detect support for the reflected property:
const supportsImagePreview =
"previewSrc" in HTMLImageElement.prototype;
Unsupported formats follow the preview-unavailable lifecycle. Support for optional compact preview formats is independent of support for previewsrc.
Developer tools may report that a preview was skipped, blocked, unsupported, or failed, but such diagnostics are not a web-observable API and are outside the HTML feature definition.
The following sections expand the optional and deferred capabilities identified in the scope table. They are independently specifiable, but their priority depends on the minimum developer-viable feature.
previewsrc accepts any image resource that the browser can decode. It therefore benefits automatically from compact image formats if those formats are specified and implemented, but the core proposal does not define, require, or special-case them.
BlurHash and ThumbHash are examples of compact encodings that could be separately specified and registered as image formats. The media type and data URL serialization below are illustrative pending that work.
An author could provide a BlurHash value:
<img
previewsrc="data:image/blurhash,LEHV6nWB2yk8pyo0adR*.7kCMdnj"
src="/images/portrait.jpg"
width="800"
height="1000"
alt="Portrait of a person">
A separately specified ThumbHash image format could use an analogous data URL with its registered media type and serialization.
Each compact format specification must define its media type and serialization; encoded-size, decoded-dimension, memory, and processing limits; malformed-input behavior; and a prohibition on nested network requests.
The core lifecycle directly replaces the preview with the final image. Developer research should determine whether customizing this handoff is necessary for adoption.
A future extension could integrate the browser-managed replacement with Element Scoped View Transitions. Unlike an author-scripted transition, the browser would initiate the transition when the final image becomes ready, while authors would customize its presentation through CSS. This explainer does not select an API for that integration. The appendix records one possible solution for further discussion.
Any extension must define how authors identify the preview and final-image snapshots, when the transition starts, and how it interacts with cancellation, source changes, reduced-motion preferences, and document lifecycle changes. It should also determine whether Image Preview needs a specific API or can use a general mechanism for browser-managed resource transitions.
The core image state and rendering model provides no preview lifecycle hook.
Without preview-specific events or state, sites cannot directly measure whether previews fetched, decoded, or displayed successfully. Resource Timing may indicate that a separate request occurred, but it does not report whether the preview decoded or was displayed. This limits preview-health monitoring but avoids adding new timing and format-support signals to the core API.
If concrete use cases establish a need for script observability, a follow-up proposal should evaluate an event, callback, or promise together with any transition object. It must also account for the additional timing and format-support information exposed by preview success, failure, and handoff timing.
Should the preview URL use a new previewsrc attribute, or should <img> reuse the existing poster name? See Reuse the poster attribute for the tradeoff.
The lowsrc name is excluded for compatibility reasons; see Historical lowsrc attribute.
The core previewsrc lifecycle can be implemented independently using existing image formats and the core handoff. The open question is whether that core alone provides enough value for developers, or whether a minimum developer-viable feature also requires compact preview formats, a customizable handoff, or both.
Developer research should determine whether developers would adopt each combination or continue using script-based preview components. Compact formats and handoff customization can be specified and shipped independently of the core lifecycle.
lowsrc attributeThe historical lowsrc attribute is direct prior art: like previewsrc, it associated a temporary image with the final image. This proposal specifies how that behavior would integrate with the modern HTML image-loading model, while retaining the costs and risks of a separate resource.
See Historical lowsrc research for its standards history, Gecko removal, and available usage evidence.
A CSS property could provide the preview instead of, or alongside, the HTML attribute:
.gallery-image {
image-preview-source:
linear-gradient(135deg, #68788c, #bcc4cc);
}
Compared with previewsrc, this has several advantages:
<image> syntax.It also has disadvantages:
HTMLImageElement property, scripts must read and update the preview through styles instead of the image API.CSS would still need to define when the preview is displayed and replaced. A CSS property could therefore be considered as an alternative or companion to previewsrc.
The browser-managed preview lifecycle could be exposed only through JavaScript:
<img
id="gallery-image"
src="/images/forest-1600.avif"
width="1600"
height="1067"
alt="Forest trail in autumn">
const image = document.querySelector("#gallery-image");
image.setPreviewSource("/images/forest-32.avif");
This could accommodate imperative options, but it would require script for a basic loading feature, delay preview discovery until script runs, and prevent server-rendered markup from expressing it. In contrast, the proposed reflected previewsrc attribute supports declarative markup while remaining dynamically updateable through previewSrc.
<picture>A special media="poster" value on a <source> element could identify that source as the preview rather than as a final-image candidate:
<picture>
<source media="poster" srcset="/images/forest-preview.avif">
<source
media="(min-width: 800px)"
srcset="/images/forest-1600.avif">
<img
src="/images/forest-800.avif"
alt="Forest trail in autumn">
</picture>
This approach keeps preview and final-image sources together and could support a different preview for each responsive or art-directed candidate. However, it would require <picture> for every image with a preview and would give media a new purpose: identifying a source's role rather than evaluating a media query.
The proposed previewsrc attribute works on any <img>, including one inside <picture>, without changing <source> selection. Version 1 provides one preview for every possible final-image candidate; responsive or source-specific preview selection can be added later if developers need it.
poster attributeThe <video> element uses poster to identify an image shown before video data is available. The same attribute could be added to <img>:
<img
poster="/images/forest-32.avif"
src="/images/forest-1600.avif"
alt="Forest trail in autumn">
Reusing poster would avoid introducing another attribute name and would build on an existing concept for temporary visual content. Related work in whatwg/html#12585 proposes responsive video posters through a child <img>, although it does not add poster to <img>.
The attribute name does not change the required implementation behavior. Adding poster to <img> would still require the fetching, scheduling, lifecycle, rendering, cancellation, and API-state rules described by this proposal. It would also need a reflected HTMLImageElement property.
previewsrc makes the relationship to src explicit, while poster reuses a familiar platform term. The choice remains an open naming question.
Progressive JPEG, interlaced image formats, and incremental decoding can reveal an increasingly complete version of one image as its bytes arrive. They avoid an additional resource request and can be the best option when the selected final format, encoder, delivery path, and browser produce useful intermediate paints.
They do not replace every preview use case:
Conversely, previewsrc can use an existing small image, be inlined, or be cached independently of the final image, and works regardless of whether the final format renders progressively. Its cost is an additional resource and possible bandwidth contention. Authors should prefer progressive delivery when it provides an adequate experience without that cost; Image Preview is complementary for cases that require an independently supplied preview.
The following alternatives consider whether native compact-format decoding, existing transition APIs, or extensible author-provided decoders could provide the intended developer experience without combining the core browser-managed lifecycle with all optional capabilities.
The browser could natively decode compact image formats without adding previewsrc or managing preview replacement. Authors would use ordinary image or canvas sources and continue coordinating preview and final content through script:
<div class="image-frame">
<img
class="preview"
src="data:image/blurhash,LEHV6nWB2yk8pyo0adR*.7kCMdnj"
alt="">
<img
class="final"
src="/images/portrait.jpg"
alt="Portrait of a person">
</div>
This would remove format-specific decoder libraries and could reduce script size and decoding cost. However, authors would still need to implement loading, replacement, cancellation, source-update handling, failures, and accessibility across multiple elements. The proposed previewsrc API adds that managed lifecycle in addition to using any image format the browser can decode.
The platform could provide native compact decoding and scoped View Transitions but leave the handoff to author script:
<img
id="photo"
src="data:image/blurhash,LEHV6nWB2yk8pyo0adR*.7kCMdnj"
alt="Portrait of a person">
const photo = document.querySelector("#photo");
const finalImage = new Image();
finalImage.src = "/images/portrait.jpg";
await finalImage.decode();
const transition = photo.startViewTransition(async () => {
photo.src = finalImage.src;
await photo.decode();
});
await transition.finished;
This gives authors full control and reuses a general transition API. It still requires every page to preload the final image, avoid stale source updates, and handle failures and cancellation. The core proposal makes the loading and replacement lifecycle browser-managed; the optional transition extension could additionally provide CSS customization while respecting reduced-motion and hidden-document behavior.
The browser could manage the preview lifecycle but invoke an author-registered decoder when it does not support the preview's media type:
<img
previewsrc="data:image/blurhash,LEHV6nWB2yk8pyo0adR*.7kCMdnj"
src="/images/portrait.jpg"
alt="Portrait of a person">
navigator.imagePreview.registerDecoder(
"image/blurhash",
async (bytes, options) => decodeBlurHash(bytes, options)
);
This would make additional formats extensible without requiring native decoders for each one. However, previews would depend on script loading and execution, and the API would need to define decoder lifetime, cancellation, security, failure handling, output dimensions, color handling, and resource limits. Native decoding provides predictable behavior without page-supplied code.
The proposal uses one <img> for the preview and final image. The existing alt attribute describes that image.
The preview must represent the same content described by the alt attribute and must not create a second accessibility node or a second announcement. The core handoff does not animate. Any optional transition mechanism must respect prefers-reduced-motion.
The proposal adds no user-facing text and no language-sensitive processing.
The guidance for localized or direction-dependent image candidates is covered by Scope and responsive images.
A URL in previewsrc can cause an additional request, with the same general privacy implications as requesting another image through src. The origin serving the preview can learn that the resource was requested.
Existing image-fetch and Resource Timing protections limit what the document can observe about the additional request, as detailed in Fetching, scheduling, and HTML integration. The core API provides no preview-specific outcome or reason when the browser omits preview work under reduced-data or resource-pressure policies. However, existing Resource Timing information can let the document infer whether a separate preview request occurred.
The core proposal adds no dedicated API exposing preview readiness, intrinsic dimensions, decode success or failure, decode duration, display, or handoff timing. Resource Timing can expose ordinary fetch and cache information, including whether a separate request occurred, but it does not report when preview decoding begins or completes.
A site may obtain an indirect estimate of related decoding work by loading the same URL into a separate Image and timing decode(), createImageBitmap(), canvas drawing, or a WebGL texture upload. These operations use a separate API path, may benefit from shared caches, and do not directly measure the browser's internal preview decode or reveal whether the preview was displayed.
Adding a preview-specific event, state property, selector, or transition signal could make decode duration, readiness, display, or format support more directly observable and requires separate privacy analysis.
previewsrc follows the same URL parsing, Content Security Policy img-src, mixed-content, and image-fetching rules as src, unless a specific difference is identified.
Preview decoding must enforce the limits applicable to the selected image format. Obsolete decoding work must be abortable, and repeated source changes must not create unbounded concurrent decoding work. Malformed or adversarial input follows the preview-unavailable lifecycle. Separately specified compact formats require their own bounds and security analysis, including a prohibition on nested network requests.
Because the preview is not exposed through image-extraction APIs, its origin does not independently affect canvas origin cleanliness. Canvas access continues to follow the origin-clean rules for the final image.
| Stakeholder | Signal | Source |
|---|---|---|
| Chromium | No position requested | — |
| Gecko | No position requested | — |
| WebKit | No position requested | — |
| WHATWG | No review requested | — |
| Web developers | No public signal gathered | — |
lowsrc sourcesImageIHTMLImgElement::lowsrcHTMLImageElementlowsrc attributelowsrc supportlowsrc compatibilitylowsrc property compatibilityThis proposal was informed by the public implementations and demonstrations cited above and throughout this explainer. In particular, the authors acknowledge the work of the BlurHash and ThumbHash projects and the application and framework developers whose preview implementations provided evidence of current practice. Their inclusion does not imply endorsement of this proposal.
The core proposal adds one reflected property:
partial interface HTMLImageElement {
[CEReactions] attribute USVString previewSrc;
};
One possible extension could integrate browser-managed preview replacement with Element Scoped View Transitions. For example, an :active-image-preview-transition pseudo-class could allow authors to customize the old preview and new final-image snapshots:
img:active-image-preview-transition::view-transition-old(root) {
animation: 200ms ease-out both image-preview-fade-out;
}
img:active-image-preview-transition::view-transition-new(root) {
animation: 200ms ease-in both image-preview-fade-in;
}
@keyframes image-preview-fade-out {
to {
opacity: 0;
}
}
@keyframes image-preview-fade-in {
from {
opacity: 0;
}
}
While the pseudo-class matches, the old(root) snapshot would represent the displayed preview and the new(root) snapshot would represent the final image. The state would end when the handoff finishes or is canceled.
This sketch is illustrative and is not part of the core proposal or a selected extension API. Further work must compare an Image Preview-specific state with a general mechanism for browser-managed resource transitions.
lowsrc researchThis section records the evidence used to evaluate lowsrc as prior art. It does not establish a single reason why the feature declined across the web.
LOWSRC originated as a Netscape extension and was later implemented by Internet Explorer. It allowed an <img> to specify a low-resolution resource that was displayed before the final src resource.
The attribute was never part of the HTML standard. DOM Level 1 exposed the lowSrc property, but DOM Level 2 removed it.
lowsrc content attribute non-conforming;HTMLImageElement.lowsrc as a URL-reflecting compatibility property.Mozilla bug 92453 documents Gecko's 2001 decision to remove its lowsrc loading behavior. The discussion identifies implementation problems, the feature's non-standard status, and questions about its usefulness.
Historical browser bug reports provide some evidence of lowsrc markup or property usage, including Ameritrade, gimp.org, and CNN. However, no reliable data on its past or current usage was found.
Although current HTML recommends progressive JPEG instead of lowsrc, no evidence was found that the availability of progressive image formats caused its decline. No evidence was found that later responsive-image features explain it either.
Clearer specification alone does not demonstrate user benefit or adoption. The same underlying risks still require evaluation:
The lowsrc name is not reused because browsers already expose HTMLImageElement.lowsrc. Reusing it would make property-based feature detection ambiguous and could give existing markup new loading behavior.
These examples correspond to patterns 1 and 2 in How authors provide previews today.
The Next.js image placeholder demo renders a single <img> whose CSS background is an inline SVG containing a tiny JPEG preview:
<img
alt="Mountains"
src="/_next/image?url=...&w=1920&q=75"
srcset="/_next/image?url=...&w=750&q=75 1x,
/_next/image?url=...&w=1920&q=75 2x"
style="
background-image: url('data:image/svg+xml,...');
background-size: cover;
background-position: 50% 50%;
background-repeat: no-repeat;
">
After the final image loads and decoding settles, Next.js removes the background preview. See the demo source, background construction, and load handoff.
On this Minds post, the site decodes BlurHash data into pixels and paints them into a canvas associated with the final image. When the image loads, Minds fades and removes the canvas. See the Minds BlurHash directive.
The resulting structure is equivalent to:
<div class="image-frame">
<canvas class="preview" width="32" height="24"></canvas>
<img class="final" src="photo.jpg" alt="...">
</div>