API Reference
Grouped by what you import: components, hooks, types, and the entries that ship separately.
Components
Section titled “Components”<ImageView>
Section titled “<ImageView>”A thumbnail that opens the viewer. It reads its surroundings and behaves accordingly:
- Inside a
Groupit is a trigger. The group owns the image set, the open state and the chrome, so everyImageViewunder it opens the same viewer and pages between its siblings. - On its own there is nothing to share with, so it supplies a
Groupand the default UI around itself. That is the one-line case.
Growing a single image into a gallery is therefore additive — wrap the
existing ImageViews in a Group; nothing gets renamed.
ImageItem plus:
| Prop | Type | |
|---|---|---|
children |
ReactElement |
The element that opens the viewer. Always child-based: ImageView renders its only child and does not accept an asChild prop. |
index |
number |
Position in the set. Registration order is used when omitted — pass it explicitly for virtualised lists or anything where mount order and visual order can diverge. |
disabled |
boolean |
|
container / labels / renderImage |
same as Group |
Only read when there is no Group above, since that is the only time this component owns a viewer. Passing them inside a Group warns in development. |
It is also the namespace: every part hangs off it as a static
(ImageView.Group, ImageView.Content, …), and each is a named export too if
you would rather import them individually and let the bundler drop what you
never render. react-img-view/primitives exports the same shape under the
same names, so a custom interface is written the same way — see
Other entry points.
<ImageView.Group>
Section titled “<ImageView.Group>”| Prop | Type | Default | |
|---|---|---|---|
images |
ImageItem[] |
triggers’ own registration order | ImageItem objects. Omit it and list ImageViews as children instead. |
open / defaultOpen |
boolean |
defaultOpen = false |
Controlled / uncontrolled. |
onOpenChange |
(open: boolean) => void |
— | |
index / defaultIndex |
number |
defaultIndex = 0 |
|
onIndexChange |
(index: number) => void |
— | |
container |
HTMLElement | null |
document.body |
Portal target — see Installation for when to point this at a themed ancestor instead. |
extensions |
Extension[] |
[] |
See Extensions below. |
labels |
Partial<ViewerLabels> |
stable English defaults | Every user-facing string. See ViewerLabels. |
<ImageView.Content>
Section titled “<ImageView.Content>”The <dialog>, portalled. Renders nothing extra — pass children to compose
your own layout, or omit it entirely and let Group supply
DefaultContent.
With the main entry, keep Content or DefaultContent as a direct child of
Group; only that level is inspected when deciding whether to append the preset.
The primitives Group never appends it.
Layout regions
Section titled “Layout regions”Header, Toolbar, Footer — plain positioning containers, all
asChild-capable <div>s with no behaviour of their own.
<ImageView.Stage>
Section titled “<ImageView.Stage>”The gesture surface. Image, Prev/Next, Loading, Error are meant to
live inside it — the toolbar’s position: absolute in the preset is
anchored to the stage specifically so anything placed below it (a caption,
Thumbnails) doesn’t push the toolbar out from under the picture.
<ImageView.Image>
Section titled “<ImageView.Image>”Renders the current slide and its immediate neighbours (so a page turn
never waits on a mount mid-gesture). overscan?: number controls how many
slides stay mounted either side of the current one (default 1).
renderImage replaces the full-size image without adding every native image
attribute to ImageItem. It receives the current item, index, whether that
slide is current, and the library’s assembled imageProps:
<ImageView.Image renderImage={({ item, imageProps }) => ( <picture> <source srcSet={`${item.src}.webp`} type="image/webp" /> <img referrerPolicy="no-referrer" {...imageProps} /> </picture> )}/>The final DOM must contain an <img> with imageProps spread onto it. Those
props carry the source, sizing styles, loading/error handlers and the plumbing
used by transforms and retry. To keep the preset chrome, pass the same renderer
to <ImageView.DefaultContent renderImage={...} />.
Status
Section titled “Status”| Part | Renders when |
|---|---|
<ImageView.Loading> |
status === 'loading' |
<ImageView.Error> |
status === 'error'. Children may be a render function: {({ retry }) => …}. |
<ImageView.Title> |
Always — current image’s name, falling back to alt. |
<ImageView.Counter> |
Always, once rendered — not in DefaultContent by default. "3 / 8", or a render function {({ index, total }) => …}. |
Controls
Section titled “Controls”Every control below is a <button> (or <a> for Download) accepting
asChild, standard button/anchor props, and publishing data-active /
data-boundary / disabled where relevant.
| Component | data-image-view-control |
Behaviour |
|---|---|---|
Close |
close |
api.close() |
Prev / Next |
prev / next |
api.prev() / api.next(); data-boundary and disabled at either end of the set |
ZoomIn / ZoomOut |
zoom-in / zoom-out |
api.zoomBy(1.4) / api.zoomBy(1/1.4); disabled at the zoom ceiling/floor |
RotateLeft / RotateRight |
rotate-left / rotate-right |
api.rotate(∓90) |
FitToWindow |
fit |
api.fit(); data-active when scale === fitScale |
ActualSize |
actual-size |
api.actualSize(); data-active when scale === 1 |
Download |
download |
An <a> with href/download set from the current image |
FitToWindow and ActualSize are deliberately two buttons, not a
segmented control — a segmented control asserts one option is always
selected, but a mid-zoom image has neither active.
<ImageView.Thumbnails>
Section titled “<ImageView.Thumbnails>”Opt-in — never rendered by DefaultContent automatically.
| Prop | Type | Default |
|---|---|---|
mode |
'auto' | 'always' | 'never' |
'auto' |
auto renders nothing for a single image, and hides under a 640px
viewport via CSS regardless of image count (a strip costs roughly 15% of a
phone’s height for little benefit at that scale). always overrides both.
<ImageView.DefaultContent>
Section titled “<ImageView.DefaultContent>”What Group renders automatically when you don’t supply your own
Content. Also usable explicitly, e.g. to turn on counter/thumbnails
without composing the rest by hand:
| Prop | Type | Default |
|---|---|---|
counter |
boolean |
false |
thumbnails |
boolean | 'auto' | 'always' | 'never' |
false — true means 'auto' |
renderImage |
same as Image |
— |
The default error is deliberately compact: visible error copy and an icon-only
retry button. Its accessible name and every other user-facing string come from
labels on Group.
useViewer()
Section titled “useViewer()”Returns the live ViewerApi — safe to call from any component inside
Group.
interface ViewerApi { readonly index: number readonly total: number readonly open: boolean readonly transform: Transform // { scale, x, y, rotation } readonly scale: number readonly rotation: number readonly fitScale: number readonly canZoomIn: boolean readonly canZoomOut: boolean readonly canPrev: boolean readonly canNext: boolean readonly status: 'idle' | 'loading' | 'ready' | 'error'
zoomTo(scale: number, options?: { origin?: { x: number; y: number }; immediate?: boolean }): void zoomBy(factor: number): void fit(): void actualSize(): void rotate(degrees: number): void go(index: number): void next(): void prev(): void close(): void retry(): void}scale/rotation/pan position are deliberately not controllable props
— they change every frame during a gesture, and making them controlled
would push a 60fps render loop onto every consumer. Read them from
useViewer(); drive them with the methods above.
useLabels()
Section titled “useLabels()”Returns the resolved ViewerLabels — the caller’s overrides already merged
over the defaults. Use it in custom controls so they localise from the same
labels prop the built-ins do.
ImageItem
Section titled “ImageItem”interface ImageItem { src: string alt?: string /** Shown by Title; falls back to alt. */ name?: string /** Skips a measurement round-trip and stops the layout jumping on load. */ width?: number height?: number /** Defaults to src. Set when the download should serve a different file. */ downloadUrl?: string}ViewerLabels
Section titled “ViewerLabels”Every string the library can render, in one record. errorTitle is the only
one DefaultContent renders as visible text — the rest are aria-labels on
otherwise icon-only controls, so leaving them untranslated is an
accessibility problem, not a cosmetic one: a screen reader announces them
verbatim whatever the page’s lang says.
Left unset, Group always uses stable English defaults. This keeps server and
hydrated markup identical and leaves language choice to the application. Pass
labels to Group (or to <ImageView>); overrides merge field by
field, so overriding one key leaves the English defaults for the rest.
interface ViewerLabels { viewer: string // aria-label on the dialog close: string prev: string next: string zoomIn: string zoomOut: string rotateLeft: string rotateRight: string fitToWindow: string actualSize: string download: string retry: string thumbnails: string // aria-label on the thumbnail strip thumbnailAt(index: number): string errorTitle: string // the one string rendered as visible text loading: string}en is exported from the main and primitives entries. Simplified Chinese is
opt-in and adds nothing to the default bundle:
import zhCN from 'react-img-view/locales/zh-CN'
function ChineseViewer({ images }) { return <ImageView.Group images={images} labels={zhCN} />}Extension
Section titled “Extension”The narrow escape hatch for keyboard behaviour composition can’t reach. Anything that needs to render belongs in the tree, while pointer gestures stay owned by Stage’s tested state machine.
interface Extension { name: string onKeyDown?(event: KeyboardEvent, api: ViewerApi): boolean | void}Return true to mark the event consumed. Pass an array via Group’s
extensions prop.
Other entry points
Section titled “Other entry points”react-img-view/primitives
Section titled “react-img-view/primitives”The same parts under the same names, without the preset. Both Group and
ImageView are the headless halves of the components the main entry exports:
Group never injects DefaultContent, and ImageView is only a trigger, so
it requires a Group rather than standing one up. That fallback is what pulls
the preset in, which is why it is not here.
Imported and composed exactly like the main entry — same names, same shape, only the import path differs:
import { ImageView } from 'react-img-view/primitives'
function Gallery({ 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.Group> )}react-img-view/imperative
Section titled “react-img-view/imperative”Import ImagePreview from this entry to open the viewer from a function
call. It adds nothing to the regular bundle:
import { ImagePreview } from 'react-img-view/imperative'
const viewer = ImagePreview.open({ images, index: 0 })viewer.go(2)viewer.next()viewer.prev()viewer.close()open() takes an options object with images: ImageItem[] and an optional
initial index. Calling it again replaces the current viewer. An empty set stays
closed, and an out-of-range index is adjusted to the nearest valid value. The
viewer cleans itself up after it is closed from the handle, the preset UI, or
Escape. Call it only in browser code such as an event handler or Effect.
open() option |
Type | Default | |
|---|---|---|---|
images |
ImageItem[] |
— | Images to open. |
index |
number |
0 |
Initial image, clamped to the available range. |
content |
ReactNode |
preset | A custom composition built from ImageView.Content and its parts. |
counter |
boolean |
false |
Show the preset counter. |
thumbnails |
boolean | 'auto' | 'always' | 'never' |
false |
Show the preset thumbnail strip. |
renderImage |
function | — | Same renderer accepted by ImageView.Image. |
container / extensions / labels |
same as Group |
— | Forwarded to the managed Group. |
The returned handle controls only the viewer created by that call; after a
later open(), an old handle is harmless. ImagePreview.close() closes the
currently active function-style viewer. Inside custom content, useViewer()
still reads state and controls zoom or rotation. A function call has no
thumbnail position, so it opens directly without the thumbnail-to-image
transition.
Passing content replaces the entire preset, so counter, thumbnails, and
renderImage apply only when the preset is used. With custom content, compose
the corresponding parts and image renderer inside it instead.
react-img-view/core
Section titled “react-img-view/core”The gesture math and state machine, exported for anyone building past what the React layer offers:
import { fitScale, tuning } from 'react-img-view/core'This entry has zero React dependency and tree-shakes independently; see the
source in src/core/ for why it stays that way.