Skip to content

Installation

React 18 or 19, and a browser. The published JavaScript targets ES2020 and relies on <dialog>.showModal(), Pointer Events, and ResizeObserver, which puts the floor at Chrome 80, Firefox 98, and Safari 15.4. Importing the preset stylesheet raises that to Chrome 111, Firefox 113, and Safari 16.2 — the toolbar surface uses CSS color-mix(). Compose your own UI from react-img-view/primitives and the lower floor applies.

Server rendering is fine; nothing runs until the viewer actually mounts. See Server rendering and Next.js below.

The React entries are published with a "use client" banner, so importing them inside a Server Component tree is enough — Next.js treats the import as a client boundary, and you do not need a wrapper module of your own.

Nothing reaches document during render. The portal target (container, defaulting to document.body) is read only after the viewer has mounted and opened, so a server render emits no dialog markup at all.

Labels are the one thing to set deliberately. Group falls back to fixed English strings rather than reading navigator.language, so server and hydrated markup always agree. Choose another language explicitly, with a value that is identical on both sides:

import { ImageView } from 'react-img-view'
import zhCN from 'react-img-view/locales/zh-CN'
import 'react-img-view/styles.css'
export default function Gallery({ images }: { images: { src: string; name: string }[] }) {
return <ImageView.Group images={images} labels={zhCN} />
}

Deriving labels from something only the browser knows — the user agent’s language, a value in localStorage — renders different text on the two sides and React will warn about it. Read the choice from the request instead: a cookie, or a locale segment in the route.

Terminal window
npm install react-img-view
Terminal window
pnpm add react-img-view

The package has no runtime dependencies of its own — the gesture engine, the animation drivers, and the modal are built on nothing but React and the DOM.

The primitive entry ships without the preset; the main entry supplies the default composition, and the stylesheet supplies its look. There are three ways to style it.

import 'react-img-view/styles.css'

Everything is driven by CSS custom properties, set on :root and on the dialog itself — never on an ancestor class, since the viewer portals out of the tree and a class-scoped rule would not follow it.

:root {
--riv-accent: #0f766e;
--riv-chrome: #ffffff;
--riv-stage: #f4f4f5;
}

Dark mode follows prefers-color-scheme automatically; force one side with data-riv-theme="light" or data-riv-theme="dark" on :root or the dialog. See Customization for the full list of properties.

If the project already uses Tailwind and shadcn’s conventions, install the Tailwind-classed version instead — real source in your repo, editable like any other component:

Terminal window
npx shadcn add https://baron04.github.io/react-img-view/r/image-view.json

The block is the same design shipped another way: the CSS preset uses custom properties, the block uses Tailwind utility classes. Only the presentation layer is copied into your project — behaviour (gestures, the modal, keyboard, the open/close animation) stays a real react-img-view dependency, the same split shadcn’s own blocks use for Radix.

Every part exposes stable data-* hooks. Controls and layout regions take asChild, while ImageView always reuses its only child. That is enough to style from scratch with attribute selectors, Tailwind’s data-[active]:… variants, or your own design system’s components. See Customization.