react-images-gallery-viewer
v0.1.1
Published
A production-ready React compound component library for rendering an image gallery modal with zooming, panning, and sidebar support.
Downloads
41
Readme
react-images-gallery-viewer
A production-ready, fully customizable React image gallery viewer built with a compound component architecture. Works seamlessly with vanilla CSS and Tailwind CSS.
Features
- 🔍 Smooth Zoom & Pan — powered by
react-zoom-pan-pinch - 🔄 Rotate & Flip — rotate left/right, flip horizontal/vertical
- 🗂 Sidebar — fully composable sidebar with mobile backdrop & close button
- 📱 Responsive — works on mobile, tablet, and desktop
- 🎨 Dual Styling — ships a base CSS file; all components accept
classNameso Tailwind classes work perfectly - 🧩 Compound Components — granular control over every piece of the UI
- 🪝 Exposed Hooks —
useGallery()anduseGalleryViewer()for advanced customization - 🔑 TypeScript — full type safety
Installation
npm install react-images-gallery-viewer lucide-react
lucide-reactis a peer dependency used for default icons.
Quick Start
import {
GalleryViewer, GalleryViewerTrigger,
GalleryViewerContent, GalleryViewerGallery,
GalleryViewerClose, GalleryViewerSidebarTrigger, GalleryViewerSidebar,
Gallery, GalleryContent, GalleryItem, GalleryImage, GalleryImageSource,
GalleryToolbar, GalleryZoomInControl, GalleryZoomOutControl,
GalleryPrevious, GalleryNext,
Image, ImageSource,
} from 'react-images-gallery-viewer';
import 'react-images-gallery-viewer/styles.css';
const images = ['https://...', 'https://...', 'https://...'];
export default function App() {
return (
<GalleryViewer>
{/* Thumbnails */}
<div className="grid grid-cols-3 gap-2">
{images.map((src, i) => (
<GalleryViewerTrigger key={i} index={i} className="aspect-square overflow-hidden rounded-lg cursor-pointer">
<Image className="w-full h-full">
<ImageSource src={src} className="w-full h-full object-cover" />
</Image>
</GalleryViewerTrigger>
))}
</div>
{/* Modal Viewer */}
<GalleryViewerContent>
<GalleryViewerGallery>
<Gallery zoomOnClick zoomMaxScale={4}>
{({ selectedIndex, isZoomed }) => (
<>
<GalleryContent>
{images.map((src, i) => (
<GalleryItem key={i} itemIndex={i + 1}>
<GalleryImage>
<GalleryImageSource src={src} alt={`Image ${i + 1}`} />
</GalleryImage>
</GalleryItem>
))}
</GalleryContent>
<GalleryToolbar>
<GalleryZoomInControl />
<GalleryZoomOutControl />
<GalleryViewerSidebarTrigger />
<GalleryViewerClose />
</GalleryToolbar>
{!isZoomed && <GalleryPrevious />}
{!isZoomed && <GalleryNext />}
</>
)}
</Gallery>
</GalleryViewerGallery>
<GalleryViewerSidebar side="right">
<p className="p-4">Your custom sidebar content here.</p>
</GalleryViewerSidebar>
</GalleryViewerContent>
</GalleryViewer>
);
}All Components
Root
| Component | Description |
|---|---|
| <GalleryViewer> | Root context provider. Wrap everything inside this. |
| <GalleryViewerTrigger index={n}> | Clickable wrapper that opens the viewer at image n. |
| <GalleryViewerContent> | The full-screen modal overlay. Renders only when open. |
| <GalleryViewerGallery> | The image area inside the modal. |
Gallery
| Component | Description |
|---|---|
| <Gallery zoomOnClick zoomMaxScale={4}> | Zoom & state provider. Accepts a render prop ({ selectedIndex, isZoomed }) => ... |
| <GalleryContent> | Wraps all slides. Animates horizontal scroll. |
| <GalleryItem itemIndex={n}> | Individual slide. Handles TransformWrapper internally. |
| <GalleryImage> | Centers image inside the slide. |
| <GalleryImageSource src="..."> | The <img> element. Accepts all standard img props. |
Toolbar Controls
| Component | Description |
|---|---|
| <GalleryToolbar> | Container for toolbar buttons. Positioned top-right by default. |
| <GalleryZoomInControl> | Zooms in the active image. |
| <GalleryZoomOutControl> | Zooms out the active image. |
| <GalleryResetControl> | Resets zoom + rotation + flip. |
| <GalleryRotateLeftControl> | Rotates image -90°. |
| <GalleryRotateRightControl> | Rotates image +90°. |
| <GalleryFlipHorizontalControl> | Flips image horizontally. |
| <GalleryFlipVerticalControl> | Flips image vertically. |
| <GalleryPrevious> | Navigate to previous image. |
| <GalleryNext> | Navigate to next image. |
| <GalleryViewerSidebarTrigger> | Toggles the sidebar open/closed. |
| <GalleryViewerClose> | Closes the modal. |
Sidebar
| Component | Description |
|---|---|
| <GalleryViewerSidebar side="right"> | Toggleable sidebar. Includes mobile backdrop + close button. |
| <SidebarHeader> | Sidebar header section. |
| <SidebarContent> | Sidebar scrollable content area. |
| <SidebarMenu> | Menu container. |
| <SidebarMenuItem> | Menu item. |
Image Helpers
| Component | Description |
|---|---|
| <Image> | Wrapper for thumbnail images. |
| <ImageSource src="..."> | <img> for thumbnails. |
| <ImageLoading> | Loading placeholder. Supports asChild. |
Hooks
Access state anywhere inside <GalleryViewer>:
import { useGalleryViewer, useGallery } from 'react-images-gallery-viewer';
// Inside GalleryViewer — works in sidebar, toolbar, etc.
function MySidebar() {
const { selectedIndex, imagesCount, sidebarOpen, setSidebarOpen } = useGalleryViewer();
return <div>Image {selectedIndex} of {imagesCount}</div>;
}
// Inside Gallery — works anywhere inside <Gallery>
function MyZoomStatus() {
const { isZoomed, rotation, flipH, flipV } = useGallery();
return <span>{isZoomed ? 'Zoomed' : 'Normal'} | Rotation: {rotation}°</span>;
}Use Cases
1. Image gallery with metadata sidebar
const images = [
{ src: '...', title: 'Sunset', description: 'A beautiful sunset over the ocean.', tags: ['Nature', 'Sky'] },
// ...
];
function MetadataSidebar() {
const { selectedIndex } = useGalleryViewer();
const img = images[selectedIndex - 1];
return (
<GalleryViewerSidebar>
<div className="p-4 border-b font-bold">{img.title}</div>
<div className="p-4 text-sm text-gray-500">{img.description}</div>
<div className="p-4 flex gap-2 flex-wrap">
{img.tags.map(t => <span key={t} className="px-2 py-0.5 bg-blue-100 text-blue-700 rounded-full text-xs">{t}</span>)}
</div>
</GalleryViewerSidebar>
);
}2. Full toolbar with all controls
<GalleryToolbar>
<GalleryZoomInControl />
<GalleryZoomOutControl />
<GalleryResetControl />
<GalleryRotateLeftControl />
<GalleryRotateRightControl />
<GalleryFlipHorizontalControl />
<GalleryFlipVerticalControl />
<GalleryViewerSidebarTrigger />
<GalleryViewerClose />
</GalleryToolbar>3. Hide nav arrows when zoomed
<Gallery>
{({ isZoomed }) => (
<>
<GalleryContent>...</GalleryContent>
{!isZoomed && <GalleryPrevious />}
{!isZoomed && <GalleryNext />}
</>
)}
</Gallery>4. Custom pagination indicator
<Gallery>
{({ selectedIndex }) => (
<>
<GalleryContent>...</GalleryContent>
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
{images.map((_, i) => (
<div key={i} className={`w-2 h-2 rounded-full transition-all ${selectedIndex === i + 1 ? 'bg-blue-500 w-4' : 'bg-white/30'}`} />
))}
</div>
</>
)}
</Gallery>5. Thumbnails with hover title overlay
<GalleryViewerTrigger index={i} className="group relative overflow-hidden rounded-xl aspect-square">
<Image className="w-full h-full">
<ImageSource src={img.thumb} className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-300" />
</Image>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-end p-3">
<span className="text-white text-sm font-semibold">{img.title}</span>
</div>
</GalleryViewerTrigger>6. Custom styled sidebar (dark theme)
<GalleryViewerSidebar className="bg-gray-900 text-white border-gray-700">
<div className="p-4 border-b border-gray-700 font-bold">Gallery</div>
<div className="p-4 text-gray-300 text-sm">Custom dark sidebar content.</div>
</GalleryViewerSidebar>7. No sidebar (toolbar-only)
<GalleryViewerContent>
<GalleryViewerGallery>
<Gallery>
{({ isZoomed }) => (
<>
<GalleryContent>...</GalleryContent>
<GalleryToolbar>
<GalleryZoomInControl />
<GalleryZoomOutControl />
<GalleryViewerClose />
</GalleryToolbar>
{!isZoomed && <GalleryPrevious />}
{!isZoomed && <GalleryNext />}
</>
)}
</Gallery>
</GalleryViewerGallery>
{/* No GalleryViewerSidebar = no sidebar */}
</GalleryViewerContent>Styling
Default CSS
Import the bundled base styles:
import 'react-images-gallery-viewer/styles.css';Override CSS variables for theming:
:root {
--rg-overlay: rgba(0, 0, 0, 0.9);
--rg-overlay-text: #ffffff;
--rg-bg: #ffffff;
--rg-text: #1e293b;
--rg-border: #e2e8f0;
--rg-primary: #3b82f6;
--rg-sidebar-width: 22rem;
--rg-sidebar-width-mobile: 100%;
}Tailwind CSS
All components accept className and use tailwind-merge internally, so Tailwind classes work perfectly:
<GalleryViewerClose className="text-red-400 hover:text-red-600" />
<GalleryPrevious className="bg-white/20 hover:bg-white/40" />
<GalleryViewerSidebar className="bg-gray-900 text-white" />License
MIT
