npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

npm license

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 className so Tailwind classes work perfectly
  • 🧩 Compound Components — granular control over every piece of the UI
  • 🪝 Exposed HooksuseGallery() and useGalleryViewer() for advanced customization
  • 🔑 TypeScript — full type safety

Installation

npm install react-images-gallery-viewer lucide-react

lucide-react is 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