Image

A responsive image in an optional fixed-ratio frame, with a loading placeholder, a failure fallback, hover zoom, parallax and scroll reveal.

<tp-image>
Image
Default density Compact density Restart from the initial state Show the copyable source

Usage

The browser still picks and loads the file: Image passes src, srcset, sizes and <source> children to a native <picture>, so lazy loading and responsive candidates work as usual. With a lazy srcset of width descriptors and no sizes, it sets sizes="auto, 100vw" so the browser fetches the laid-out width. Set alt="" for a decorative image.

An explicit sizes is passed through unchanged. An eager image without sizes keeps the browser default (100vw).

Ratio and fit

ratio (width ÷ height) reserves a fixed frame before the image loads, so nothing shifts. fit decides how the picture fills it, as object-fit does. Round the frame with border-radius on the host.

Setting an invalid ratio throws RangeError and keeps the previous value. Without ratio, the image keeps its own proportions. Give width and height (or set them on the selected <source>) so the space is reserved before the image loads. When the host has an explicit block size, fit applies within it.

Ratio and fit
Default density Compact density Restart from the initial state Show the copyable source

Placeholder and fallback

While loading, Image shows a pulsing skeleton; placeholder="spinner" adds a spinner and none shows nothing. If the image fails or has no source, it shows a muted surface with a broken-image icon, or your fallback slot content, which stays exposed to assistive technology.

Once loaded, the image fades in. A cached image appears without a loading phase or fade. The image stays hidden until it loads, so a broken-image glyph never appears.

The placeholder and the default fallback are hidden from assistive technology and never announce anything, so a gallery doesn’t create dozens of live regions. The frame reports aria-busy while loading.

Image paints no surface of its own. The placeholder and fallback surfaces are the composed Skeleton, so theme tokens such as --tp-muted apply.

Fallbacks
Default density Compact density Restart from the initial state Show the copyable source
No cover yet

For a gentler load, put a tiny blurred version of the photo in the placeholder slot; it shows until the full image is ready.

Loading placeholders
Default density Compact density Restart from the initial state Show the copyable source

Hover zoom

zoom="in" scales the picture up while the pointer hovers, and zoom="out" settles it from enlarged to natural size. The frame never changes size. Set zoomed to drive the effect from a surrounding card instead.

Hover zoom applies only to devices that can hover, and it never changes layout or the clipping frame. Tune it with --tp-image-zoom-duration and --tp-image-zoom-easing.

Hover zoom
Default density Compact density Restart from the initial state Show the copyable source

Here the whole card drives the zoom, so the photo responds wherever the pointer enters the card.

Card hover
Default density Compact density Restart from the initial state Show the copyable source

Quiet floor

12 bookable desks with natural light.

Reveal and groups

reveal animates an image into place the first time it scrolls into view, from tokens such as fade up or zoom-in. Wrap a set in tp-image-group to wait until every image has loaded, then reveal them in order, stagger milliseconds apart. A reveal that would start during a same-document view transition waits for it to finish.

The group starts fetching every member once it is within 25% of the viewport, so lazy members don’t hold the set back. It reveals nothing until every member has settled (loaded and decoded, or failed and showing its fallback). Members that have no reveal of their own use the group’s reveal. Inside a group, members never trigger their own reveal; the group owns their timing. Lay the group out like any block, for example as a grid.

Members’ events bubble through the group with the same names, so check event.target === group to handle only the group’s.

Staggered group
Default density Compact density Restart from the initial state Show the copyable source

Images outside a group reveal on their own as each loads. Tune them with --tp-image-reveal-duration, --tp-image-reveal-easing and --tp-image-reveal-delay, set on a container to apply to every image inside; a growing delay staggers a grid. Add reveal-repeat to replay the reveal each time an image scrolls back into view. Scroll the box to see it.

Reveal timing and repeat
Default density Compact density Restart from the initial state Show the copyable source

Keep scrolling

The reveal never starts before the image is ready. An image that enters while still loading stays in its start state until it has loaded and decoded, then reveals if it is still in view. An image that fails to load reveals its fallback, and one with no source reveals at once. Add reveal-repeat to play it again on every entry: once the image is fully out of view, it returns to its start state instantly, out of sight, and reveals again when it is back at least 10% inside the viewport. That margin stops it flickering at an edge.

To sequence images with surrounding text, wrap them in a Scroll trigger. It waits for the group’s images to load, then reveals the heading, the images and the caption in order.

Headline, group and caption
Default density Compact density Restart from the initial state Show the copyable source

Spaces that work the way you do

Open desks, quiet rooms and a terrace, all bookable from the app.

An image belongs to its nearest group or trigger, and a group inside a trigger or another group is one member of it, playing its own stagger after the delay it receives. Images added later join the set and reveal as soon as they settle. For other motion, hold the group, wait for what you need, then clear reveal-hold. Inside a scrubbing trigger the scroll position drives the image’s reveal (delay, duration and easing included) instead of time.

Under reduced motion, from motion-policy="reduce" or the system preference, images appear already revealed and zoom is instant.

Parallax

parallax moves the picture inside its frame as the page, or the nearest scrolling container, scrolls past. The image is enlarged by parallax-depth so the frame edges never show; add zoom-in or zoom-out to scale it as it travels.

Clipping wrappers that don’t scroll, such as a card with overflow: hidden, are skipped. Directions are physical and don’t flip for right-to-left text, and any direction works with either axis; left and right suit a horizontal strip. Parallax only moves while the container actually scrolls past the image. An image is placed shortly before it scrolls into view, so it never jumps when it appears.

Parallax in a scroller
Default density Compact density Restart from the initial state Show the copyable source

Scroll zoom scales the image instead of moving it: zoom-in grows it as it passes through the box, and zoom-out settles it from enlarged to natural size. Combine zoom with a direction, such as up zoom-in, to zoom and drift at once.

Scroll zoom in a vertical scroller
Default density Compact density Restart from the initial state Show the copyable source

Scroll zoom scales the picture inside the moving layer, so it also combines with hover zoom, and it only ever enlarges, so the frame edges never show.

A container that scrolls only sideways measures progress along its inline axis, so parallax="left" drifts each image as the strip moves.

Horizontal strip
Default density Compact density Restart from the initial state Show the copyable source

parallax-smoothing, from 0 to 1, makes the picture trail the scroll and ease into place after it stops. In this scroller the first image is locked to the scroll and each following one is smoother.

Smoothing
Default density Compact density Restart from the initial state Show the copyable source

The value is the share of the remaining distance still left after one 60 fps frame: 0.5 trails slightly, 0.9 glides slowly. It is clamped to 0.98, and it behaves the same at any frame rate.

Art direction

<source> children give the native picture other candidates, such as a tighter crop for narrow screens through media. For a hero that is the largest content on first paint, set loading="eager" and fetchpriority="high".

Use media to switch crops between layouts and type for AVIF or WebP with a fallback. The authored <source> elements never request anything themselves. Adding, removing or editing a source reloads the image.

When the browser switches candidates after loading, for example when you resize across a media breakpoint, currentSrc updates and the status stays loaded.

Hero with a narrow-screen crop
Default density Compact density Restart from the initial state Show the copyable source

Performance

Image is built for pages with hundreds of images. Placeholders that are off screen pause their animation, and no will-change is applied, so idle images create no compositing layers.

API Reference

Every element also accepts motion-policy.

Attributes

AttributePropertyTypeDefaultDescriptionsrcsrcstring''The image URL.altaltstring''What the image shows, for assistive technology; empty marks it decorative.srcsetsrcSetstring''Responsive image candidates; with width descriptors and lazy loading, sizes defaults to the rendered width.sizessizesstring''The image's display sizes for choosing among srcset candidates.crossorigincrossOrigin'' | 'anonymous' | 'use-credentials'''The image request's CORS mode.referrerpolicyreferrerPolicyReferrerPolicy''The referrer policy of the image request.fetchpriorityfetchPriority'' | 'high' | 'low' | 'auto'''A hint for how urgently to fetch the image: high for the largest image above the fold.widthwidthnumber | undefined—The intrinsic width in pixels, which reserves space before the image loads.heightheightnumber | undefined—The intrinsic height in pixels, which reserves space before the image loads.loadingloading'lazy' | 'eager''lazy'Defers loading until the image nears the viewport; eager for images visible on load.ratiorationumber | undefined—Width divided by height; unset uses the image's intrinsic geometry.fitfitImageFit'cover'How the image fills its frame: cropped, letterboxed, stretched or at its own size.placeholderplaceholderImagePlaceholder'skeleton'The surface shown while the image loads.zoomzoomImageZoom'none'Scales the image in or out on hover, inside its frame.zoomedzoomedbooleanfalseApplies the zoom without hover, for example while an enclosing card is hovered.parallaxparallaxImageParallax'none'Scroll-linked effects: one direction the image travels, a scroll zoom, or both, such as up zoom-in.parallax-depthparallaxDepthnumber0.3How strongly the parallax image is enlarged and travels, from 0 to 1.parallax-smoothingparallaxSmoothingnumber0How far parallax and scroll zoom trail the scroll, from 0 (locked to it) toward 1.revealrevealstring''Effects played as the image first enters the viewport: any of fade, up, down, left, right, zoom-in and zoom-out.reveal-repeatrevealRepeatbooleanfalseReveal again on every viewport entry instead of only the first.reveal-holdrevealHoldbooleanfalseWhile set, a ready reveal waits in its start state; clearing it plays the reveal.

Properties

PropertyTypeDefaultDescriptionimageLoadingStatusread-onlyImageLoadingStatus—Loading status of the current source.currentSrcread-onlystring—The candidate the browser selected for the current source.revealedread-onlyboolean—Whether the reveal has played (false again after a repeat reset).

Events

EventTypeDescriptiontp-loading-status-changeCustomEvent<{ status: ImageLoadStatus; }>The current source's loading status changed; detail.status is the new status.tp-reveal-changeCustomEvent<{ revealed: boolean; effect: string; }>The reveal started, or reset for a repeat; detail.revealed and the detail.effect played.tp-reveal-change-completeCustomEvent<{ revealed: boolean; }>The reveal finished settling; detail.revealed.

Slots

SlotDescriptiondefault<source> children (media, type, srcset, sizes, width, height), in priority order.placeholderReplaces the default loading placeholder.fallbackReplaces the default failure fallback.

CSS parts

PartDescription::part(frame)The clipping frame (Aspect-ratio box when ratio is set).::part(media)The layer that zoom and parallax transform.::part(picture)The native image.::part(placeholder)The loading surface.::part(fallback)The failure surface.

CSS custom properties

PropertyDefaultDescription--tp-image-position—Object position of the image. Default 50% 50%.--tp-image-zoom-scale—Hover zoom scale. Default 1.1.--tp-image-reveal-distance—Reveal travel. Default var(--tp-space-6).--tp-image-reveal-scale—Reveal scale offset for zoom-in/zoom-out. Default 0.08.--tp-image-reveal-delay—Reveal delay, for staggering. Default 0s.--tp-image-reveal-duration—Reveal duration. Default var(--tp-duration-slow).--tp-image-reveal-easing—Reveal easing. Default var(--tp-easing-enter).--tp-image-zoom-duration—Hover zoom duration. Default var(--tp-duration-slow).--tp-image-zoom-easing—Hover zoom easing. Default var(--tp-easing-enter).

<tp-image-group>

Attributes

AttributePropertyTypeDefaultDescriptionstaggerstaggernumber0Milliseconds between consecutive member reveals, in document order; 0 reveals in sync.stagger-fromstaggerFromStaggerFrom'first'The member the stagger starts from.revealrevealstring''Default reveal effect for members without their own.reveal-repeatrevealRepeatbooleanfalseReplay the group reveal each time the group re-enters view.reveal-holdrevealHoldbooleanfalseWhile set (or while any member holds), the group waits in its start state.

Properties

PropertyTypeDefaultDescriptionloadingStatusread-onlyImageGroupLoadingStatus—Aggregate loading status of the members.imagesread-onlyHTMLElement[]—The member images, in document order.revealedread-onlyboolean—Whether the group sequence has played (false again after a repeat reset).

Events

EventTypeDescriptiontp-loading-status-changeCustomEvent<{ status: string; loaded: number; failed: number; total: number; }>{ status, loaded, failed, total } when the aggregate status changes.tp-reveal-changeCustomEvent<{ revealed: boolean; }>{ revealed } when the group sequence starts or resets.tp-reveal-change-completeCustomEvent<{ revealed: boolean; }>{ revealed } after the last member's reveal settles.

Slots

SlotDescriptiondefaultContent, including the member Images at any depth.

Components, widgets and patterns that use this one.