Quick Start
Single image
Section titled “Single image”Wrap any element — the thumbnail can be a plain <img>, next/image,
<picture>, or a component from your own design system. ImageView only
attaches behaviour and a ref; it never renders an <img> of its own for
the trigger.
import { ImageView } from 'react-img-view'import 'react-img-view/styles.css'
function SingleImagePreview({ image }: { image: { thumb: string; full: string; name: string } }) { return ( <ImageView src={image.full} alt={image.name} name={image.name}> <img src={image.thumb} alt={image.name} /> </ImageView> )}
Several images, shared viewer
Section titled “Several images, shared viewer”Group collects every ImageView beneath it into one set. Clicking any one
opens the viewer positioned on that image, with arrow keys and swipe moving
between the rest. No Content is written here — Group supplies the same
polished default UI itself.
import { ImageView } from 'react-img-view'import 'react-img-view/styles.css'
function ImageList({ images }: { images: { src: string; name: string }[] }) { return ( <ImageView.Group images={images}> <div className="grid grid-cols-4 gap-3"> {images.map((image, i) => ( <ImageView key={image.src} index={i} {...image}> <img src={image.src} alt={image.name} className="h-24 w-full object-cover" /> </ImageView> ))} </div> </ImageView.Group> )}



Turning on the counter or the thumbnail strip
Section titled “Turning on the counter or the thumbnail strip”Both are off by default — for a short set, the arrows say enough. Turn them on for a longer set, where the arrows alone do not say enough:
<ImageView.Group images={images}> {/* triggers */} <ImageView.DefaultContent counter thumbnails /></ImageView.Group>Imperative API
Section titled “Imperative API”Use the imperative API when you do not want to manage open and index
yourself:
import { ImagePreview } from 'react-img-view/imperative'
function ViewButton({ images }) { return <button onClick={() => ImagePreview.open({ images, index: 2 })}>View image 3</button>}Pass the images and initial index to open(). Calling it again replaces the
current viewer. It returns a handle with close(), go(), next(), and
prev(); ImagePreview.close() closes whichever function-style viewer is
active. The viewer cleans itself up after closing.
An imperative call has no corresponding thumbnail element, so the viewer opens directly without the thumbnail-to-image transition.
Controlled open state
Section titled “Controlled open state”Open the viewer from anywhere else in the app — a “View” button in a table
row, say — by controlling open and index yourself. Prefer this form when
the surrounding component also needs to own those values:
const [open, setOpen] = useState(false);const [index, setIndex] = useState(0);
<ImageView.Group images={images} open={open} index={index} onOpenChange={setOpen} onIndexChange={setIndex}> {/* triggers are optional when you open it programmatically */}</ImageView.Group>
<Button onClick={() => { setIndex(2); setOpen(true); }}> View image 3</Button>Full composition
Section titled “Full composition”Build your own layout from the same parts DefaultContent uses. Controls and
layout regions take asChild; ImageView always reuses its child. Every control
publishes data-active / data-boundary / data-disabled for styling.
Keep Content or DefaultContent as a direct child of the main-entry Group.
That is the boundary it checks before deciding whether to append the preset.
import { ImageView } from 'react-img-view/primitives'
function CustomViewer({ images }) { return ( <ImageView.Group images={images}> {images.map((image, i) => ( <ImageView key={image.src} index={i} {...image}> <img src={image.src} alt={image.alt} /> </ImageView> ))}
<ImageView.Content className="riv-dialog"> <ImageView.Header className="riv-header"> <ImageView.Close>Close</ImageView.Close> <ImageView.Title /> <ImageView.Download>Download</ImageView.Download> </ImageView.Header>
<ImageView.Stage className="riv-stage"> <ImageView.Image /> <ImageView.Prev>‹</ImageView.Prev> <ImageView.Next>›</ImageView.Next> <ImageView.Loading>Loading…</ImageView.Loading> <ImageView.Error> {({ retry }) => ( <div> <p>This image couldn't be loaded</p> <button onClick={retry}>Retry</button> </div> )} </ImageView.Error>
<ImageView.Toolbar className="riv-toolbar"> <ImageView.ZoomOut /> <ImageView.ZoomIn /> <ImageView.RotateLeft /> <ImageView.RotateRight /> <ImageView.FitToWindow /> <ImageView.ActualSize /> </ImageView.Toolbar> </ImageView.Stage> </ImageView.Content> </ImageView.Group> )}See Customization for replacing individual buttons, adding your own, and translating every string.
Reading viewer state
Section titled “Reading viewer state”useViewer() works from anywhere inside Group — a custom control, a
readout, an analytics hook:
import { useViewer } from 'react-img-view/primitives'
function ZoomReadout() { const viewer = useViewer() return <span>{Math.round(viewer.scale * 100)}%</span>}See the API reference for everything useViewer()
returns and every prop each part accepts.
Keyboard shortcuts
Section titled “Keyboard shortcuts”| Key | Action |
|---|---|
← / → |
Previous / next image |
Home / End |
First / last image |
+ / - |
Zoom in / out |
0 |
Fit to window |
1 |
Actual size (1:1) |
R / Shift+R |
Rotate right / left |
Esc |
Close |