Zoom Lens
An image magnifier whose lens follows the cursor and magnifies in place, or presents the magnified result in a side panel.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add zoom-lens.enZoom Lens is a React image zoom component built with Tailwind CSS and Motion. A circular or square lens follows the cursor and magnifies the area under it in place, or you can switch to panel mode and show the magnified result in a separate panel next to the image.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ZoomLens/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- Main image on an e-commerce product page, so shoppers can inspect fabric texture, stitching or a metal finish.
- Portfolio or photography sites showing details of a high-resolution original.
- Spec pages with close-ups of circuit boards or parts whose labels are too small to read.
- Previews of dense images such as maps, floor plans or design mockups.
Installation
npx webberui@latest add https://webberui.com/r/zoom-lens.en.jsonOr, once registries are configured in components.json, install it as @webberui/zoom-lens.
Usage
import { ZoomLens } from "@/components/ui/zoom-lens";
<ZoomLens
src="/product.jpg"
alt="Product detail shot"
className="w-full max-w-md"
/>Side panel mode
The panel variant marks a viewfinder box on the original image and shows the magnified result in a side panel — a good fit for e-commerce product images.
<ZoomLens
src="/product.jpg"
alt="Product detail shot"
variant="panel"
panelPosition="right"
zoom={2.6}
size={200}
className="w-64"
/>Examples
Square lens with higher magnification
shape="square" with a larger zoom works well for images with small text or fine texture.
<ZoomLens
src="/pcb-board.jpg"
alt="Motherboard component close-up"
shape="square"
zoom={4}
size={180}
className="w-full max-w-lg"
/>Product gallery with thumbnails
When src changes, the lens and magnified background use the new image, so you can drive it from a thumbnail row.
"use client";
import { useState } from "react";
import { ZoomLens } from "@/components/ui/zoom-lens";
const photos = ["/shoe-side.jpg", "/shoe-top.jpg", "/shoe-sole.jpg"];
export function ProductGallery() {
const [current, setCurrent] = useState(photos[0]);
return (
<div className="space-y-3">
<ZoomLens
src={current}
alt="Running shoe product photo"
variant="panel"
panelPosition="right"
className="w-72"
/>
<div className="flex gap-2">
{photos.map((p) => (
<button key={p} type="button" onClick={() => setCurrent(p)}>
<img src={p} alt="" className="size-14 rounded-md object-cover" />
</button>
))}
</div>
</div>
);
}Hide the caption while zooming
onZoomChange fires once when magnification turns on and once when it turns off, so you can sync other UI with it, such as fading out a caption.
const [zooming, setZooming] = useState(false);
<figure>
<ZoomLens
src="/watch-dial.jpg"
alt="Watch dial detail"
onZoomChange={setZooming}
className="w-full max-w-md"
/>
<figcaption className={zooming ? "opacity-0" : "opacity-100"}>
Move your mouse to inspect the dial markings
</figcaption>
</figure>Props
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Image source URL |
alt | string | "" | Alternative text for the image |
variant | "lens" | "panel" | "lens" | How the magnification is presented |
zoom | number | 2.5 | Magnification factor |
size | number | 160 | Lens diameter (lens) / panel edge length (panel), in px |
shape | "circle" | "square" | "circle" | Lens shape (lens only) |
panelPosition | "right" | "left" | "top" | "bottom" | "right" | Panel position (panel only) |
disabled | boolean | false | Disable the magnification interaction and show only the original image |
onZoomChange | (active: boolean) => void | — | Callback when the magnification on/off state changes |
className | string | — | className for the image frame; set the image size here |
imageClassName | string | — | className for the <img> |
How it works
- Give a width in
className(such asw-full max-w-md) and the image expands at its natural ratio withh-auto; the component measures the displayed size through aResizeObserverand computes the magnified background from it. - The viewfinder's center is clamped inside the image's bounds, so the magnified content never exposes a blank edge.
- The position follows smoothly via a Motion spring;
lensandpanelshare the same background coordinate math, so the factor and size behave identically. - Mouse, touch, and stylus are all supported (Pointer Events); on touch you move the viewfinder by dragging, and it retracts when you let go.
Accessibility
- The
<img>keeps itsalt, and the magnification interaction is progressive enhancement, so assistive technology still announces the image normally. - The lens, the viewfinder box, and the magnification panel are all decorative layers marked
aria-hidden, so they do not interfere with the reading order. - When the user has "reduce motion" enabled at the system level, the tracking and the fade in/out land instantly, without using a spring or scaling.
- The component cleans up its
ResizeObserver, so no listeners are left behind on unmount.
FAQ
Does it work with the Next.js App Router?
Yes. The source starts with "use client", so it is a Client Component; import it from a Server Component page as usual.
How do I change the magnification and lens size?
Use zoom for the factor (default 2.5) and size for the lens diameter or panel edge (default 160 px). Set the image width itself in className, for example w-full max-w-md.
Does it work on phones?
Yes. It uses Pointer Events: on touch you drag to move the viewfinder and it retracts when you lift your finger, while with a mouse it stays open until the cursor leaves the image. Pass disabled when you want to turn magnification off.
Do I need a separate high-resolution image?
The component uses the same src as the magnified background and sizes it as the displayed size times zoom. The higher the source resolution, the sharper the magnified view; a low-resolution image will look blurry when zoomed.
What are the dependencies?
motion, lucide-react, clsx and tailwind-merge. If you would rather not use the CLI, the Manual tab in Installation lets you copy the source.