<ZoomImage
src="https://cdn.vortex.design/docs/components/image/melbourne.jpg"
alt="Flinders Street Station in Melbourne"
width="300px"
aspectRatio={1.5}
/>Features
- Browses related images with
ZoomImage.Group. - Supports responsive sizes, cropping, and borders.
- Keeps a margin between the enlarged image and the viewport.
- Reports zoom changes and opens or closes through a ref.
- Renders a custom image element with
renderImageSlot.
Size and Object Fit
Set the size with width, height, maxWidth, or aspectRatio; each accepts a breakpoint object. objectFit controls the crop, and borderRadius and bordered style the image.
import { Stack, ZoomImage } from "@vortexlabs/vortex";
<Stack direction="row" wrap gap={4}>
<ZoomImage
src="https://cdn.vortex.design/docs/components/image/melbourne.jpg"
alt="Flinders Street Station, square crop"
width={{ xs: "8rem", md: "12rem" }}
aspectRatio={1}
objectFit="cover"
borderRadius="md"
bordered
/>
<ZoomImage
src="https://cdn.vortex.design/docs/components/image/melbourne.jpg"
alt="Flinders Street Station, full image"
width={{ xs: "8rem", md: "12rem" }}
aspectRatio={1}
objectFit="contain"
borderRadius="md"
bordered
/>
</Stack>Disabled
Set disabled to show the image without letting it open.
<ZoomImage
src="https://cdn.vortex.design/docs/components/image/melbourne.jpg"
alt="Flinders Street Station preview"
width="16rem"
maxWidth="100%"
aspectRatio={16 / 9}
disabled
/>Zoom Margin
zoomMargin sets the space between the enlarged image and the screen edge. Pass spacing units or a CSS length; the default is 6.
<ZoomImage
src="https://cdn.vortex.design/docs/components/image/melbourne.jpg"
alt="Flinders Street Station with a generous zoom margin"
width="16rem"
maxWidth="100%"
zoomMargin="3rem"
/>Events and Ref
onZoomChange tells you when the image opens or closes. Pass a ref to open or close it from another control, like a button.
"use client";
import { Button, Stack, Text, ZoomImage } from "@vortexlabs/vortex";
import type { ZoomImageRef } from "@vortexlabs/vortex";
import { useRef, useState } from "react";
export const ZoomImageControls = () => {
const ref = useRef<ZoomImageRef>(null);
const [zoomed, setZoomed] = useState(false);
return (
<Stack gap={3}>
<ZoomImage
ref={ref}
src="https://cdn.vortex.design/docs/components/image/melbourne.jpg"
alt="Flinders Street Station panorama"
width="16rem"
maxWidth="100%"
onZoomChange={setZoomed}
/>
<Button onClick={() => ref.current?.open()}>Enlarge panorama</Button>
<Text>Image is {zoomed ? "enlarged" : "inline"}.</Text>
</Stack>
);
};Custom Rendering
Use renderImageSlot to supply your own image element, such as a framework image component. Pass all the attributes it receives, including the ref, to that element.
<ZoomImage
src="https://cdn.vortex.design/docs/components/image/melbourne.jpg"
alt="Flinders Street Station with custom image rendering"
width="16rem"
maxWidth="100%"
renderImageSlot={(attributes) => <img {...attributes} decoding="async" />}
/>Fallback
If the image fails to load, ZoomImage shows a placeholder and can't be opened. Use onError to react to the failure.
<ZoomImage
src="https://cdn.vortex.design/docs/components/image/missing.webp"
alt="Station photo that failed to load"
width="16rem"
maxWidth="100%"
aspectRatio={16 / 9}
/>Composition
ZoomImage.Group
Wrap related images in ZoomImage.Group and use ZoomImage.Item for each one. People can then move between the images without closing the enlarged view.
In a grid, give each item width="100%" and a block-level button through attributes, so it fills its cell.
"use client";
import { Grid, ZoomImage } from "@vortexlabs/vortex";
const unsplash = (id: string) =>
`https://images.unsplash.com/photo-${id}?q=80&w=1600&auto=format&fit=crop`;
const photos = [
{
src: unsplash("1506973035872-a4ec16b8e8d9"),
alt: "Aerial view of a beach with turquoise water",
},
{
src: unsplash("1469474968028-56623f02e42e"),
alt: "Golden light through trees in a misty forest",
},
{
src: unsplash("1501854140801-50d01698950b"),
alt: "Lush green mountain valley",
},
{
src: unsplash("1432405972618-c60b0225b8f9"),
alt: "Waterfall cascading over mossy rocks",
},
{
src: unsplash("1464802686167-b939a6910659"),
alt: "Milky Way galaxy over dark landscape",
},
{
src: unsplash("1441974231531-c6227db76b6e"),
alt: "Sunlit forest path with tall trees",
},
];
export const ZoomImageGallery = () => {
return (
<ZoomImage.Group>
<Grid display="grid" columns={4} gap={2} width="100%" maxWidth="480px">
{photos.map((photo, index) => {
const wide = index % 3 === 0;
return (
<Grid.Item key={photo.src} columns={wide ? "span 2" : undefined}>
<ZoomImage.Item
src={photo.src}
alt={photo.alt}
width="100%"
aspectRatio={wide ? 2 : 1}
borderRadius="sm"
attributes={{ style: { display: "block", width: "100%" } }}
/>
</Grid.Item>
);
})}
</Grid>
</ZoomImage.Group>
);
};Group Events and Ref
onIndexChange reports the index of the open image, or null when the view closes. A ZoomImageGroupRef opens the view at an index with open(index).
"use client";
import { Button, Stack, Text, ZoomImage } from "@vortexlabs/vortex";
import type { ZoomImageGroupRef } from "@vortexlabs/vortex";
import { useRef, useState } from "react";
// photos is the array from the gallery example above.
export const ZoomImageGroupControls = () => {
const ref = useRef<ZoomImageGroupRef>(null);
const [index, setIndex] = useState<number | null>(null);
return (
<Stack gap={3}>
<ZoomImage.Group ref={ref} onIndexChange={setIndex}>
<Stack direction="row" wrap gap={2}>
{photos.slice(0, 3).map((photo) => (
<ZoomImage.Item
key={photo.src}
src={photo.src}
alt={photo.alt}
width="8rem"
aspectRatio={1}
borderRadius="sm"
/>
))}
</Stack>
</ZoomImage.Group>
<Button onClick={() => ref.current?.open(2)}>Open the third image</Button>
<Text>{index === null ? "No image open." : `Showing image ${index + 1}.`}</Text>
</Stack>
);
};Accessibility
Enter or Space while an image is focused opens the enlarged view.Escape closes the enlarged view and returns focus to the element that opened it, or in a group to the last image shown.ArrowLeft and ArrowRight show the previous and next image.alt for every image; it names both the zoom button and the enlarged view.