Skip to content
WANCRAFT

B.67Cards & Containers

Zoom Image

Click an image to zoom it from its exact on-page position to fit the viewport, in a native modal dialog.

NewReactCSSTailwindintermediate

Overview

Zoom Image is a medium-style lightbox for a single image. Clicking (or pressing Enter / Space on) the thumbnail opens a native modal <dialog> and grows a copy of the image out of the thumbnail with a FLIP transform; an animated clip-path un-crops it, so even a thumbnail cropped to 1:1 expands seamlessly into the full frame. The backdrop fades in, an optional caption sits under the image, and clicking anywhere, pressing Escape or scrolling sends it back to wherever the thumbnail is now. With reduced motion it opens and closes instantly.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/zoom-image.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/zoom-image.json

Method 02 · Copy and paste

  1. 1. No extra dependencies — React and Tailwind CSS only.
  2. 2. Copy the source from the Code tab into components/ui/zoom-image.tsx.
  3. 3. Import it and adjust props.
Dependencies
None
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { ZoomImage } from "@/components/ui/zoom-image";

export function ZoomImageExample() {
  return (
    <div className="w-full max-w-md">
      <ZoomImage
        src="/images/terraces.jpg"
        alt="Rice terraces at dusk"
        width={1200}
        height={800}
      />
    </div>
  );
}

03Props

PropTypeDefaultDescription
src / altstring—Required. Image source and alternative text (also names the dialog when there is no caption).
zoomSrcstring—Higher-resolution source for the zoomed view. Defaults to src.
width / heightnumber—Intrinsic size: reserves layout space and is used when the natural size is unknown.
captionstring—Caption under the zoomed image; labels the dialog.
showCaptionbooleantrueAlso show the caption under the thumbnail.
aspectRatiostring"auto"Crop the thumbnail ("1/1", "4/3", "16/9"…); the zoom un-crops it.
marginnumber40Space kept around the zoomed image, in px.
durationnumber360Zoom transition length in ms (closing is slightly faster).
roundednumber12Thumbnail corner radius in px; the zoom morphs it.
backdrop"theme" | "dark""theme"Page-toned backdrop that follows light / dark, or always dark.
closeLabelstring"Close"Accessible name of the close button.
onZoomChange(zoomed: boolean) => void—Called when the image opens and after it has closed.
...propsHTMLAttributes<HTMLElement>—Forwarded to the root <figure>.

04Features

  • 01FLIP zoom from the thumbnail's exact position and size to fit the viewport
  • 02Animated clip-path un-crops object-fit: cover thumbnails and morphs the corner radius
  • 03Closes on click, Escape or page scroll, animating back to where the thumbnail is now
  • 04Interrupting the zoom reverses it from its current point
  • 05Re-fits on resize; optional hi-res zoomSrc and caption
  • 06Web Animations API only — no dependencies beyond React
motion · tweeninput · clickinput · keyboardinput · scroll

05Accessibility

  • The thumbnail is a native <button> (Enter / Space) with aria-haspopup="dialog" and aria-expanded; its name is the alt text plus "enlarge image".
  • The zoomed view is a native modal <dialog> opened with showModal(): top layer, inert page, focus moved to a labelled close button and returned to the thumbnail on close.
  • The dialog is named by the caption, or by the alt text when there is none; alt text is required.
  • With prefers-reduced-motion the zoom opens and closes instantly.

06Limitations

  • Images are scaled to fit the viewport, so small raster images can be upscaled; pass a larger zoomSrc for sharp results.
  • The thumbnail uses object-fit: cover; contain-fitted thumbnails are not matched exactly.