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

@chrisader/react-lightbox

v0.2.0

Published

Composable React lightbox components with animated image viewing, zoom, and gallery navigation.

Readme

react-lightbox

Composable React lightbox primitives for galleries, zoom, and animated image transitions.

Install

npm install @chrisader/react-lightbox

react and react-dom are peer dependencies.

Quick start

Import the stylesheet once:

import "@chrisader/react-lightbox/styles.css";

Wrap your gallery with PhotoProvider, then compose each lightbox with PhotoView:

import { PhotoProvider, PhotoView } from "@chrisader/react-lightbox";

export function Gallery() {
  return (
    <PhotoProvider>
      <PhotoView.Backdrop />

      <PhotoView>
        <PhotoView.Thumbnail src="/thumb-1.jpg" alt="Preview" />
        <PhotoView.Content>
          <PhotoView.Image src="/image-1.jpg" alt="Full image" />
          <PhotoView.Close />
          <PhotoView.Previous />
          <PhotoView.Next />
          <PhotoView.Current />
          <PhotoView.Total />
        </PhotoView.Content>
      </PhotoView>
    </PhotoProvider>
  );
}

How it works

  • PhotoProvider stores gallery state and settings.
  • PhotoView.Backdrop renders the shared overlay. Place it once as a direct child of PhotoProvider, not inside PhotoView.
  • Each PhotoView registers one item in that gallery.
  • PhotoView.Thumbnail opens the matching lightbox view.
  • PhotoView.Content renders the modal layer in a portal.
  • PhotoView.Image, Close, Previous, Next, Current, and Total are composable parts inside the modal.

Usage patterns

Multiple images in one gallery

Use one PhotoProvider around all related PhotoView items so navigation works across the full set.

import { PhotoProvider, PhotoView } from "@chrisader/react-lightbox";

const photos = [
  {
    id: "mountain",
    thumb: "/thumbs/mountain.jpg",
    full: "/photos/mountain.jpg",
    alt: "Mountain lake",
  },
  {
    id: "forest",
    thumb: "/thumbs/forest.jpg",
    full: "/photos/forest.jpg",
    alt: "Forest trail",
  },
  {
    id: "coast",
    thumb: "/thumbs/coast.jpg",
    full: "/photos/coast.jpg",
    alt: "Rocky coast",
  },
];

export function PhotoGrid() {
  return (
    <PhotoProvider>
      <PhotoView.Backdrop />

      <div className="grid">
        {photos.map((photo) => (
          <PhotoView key={photo.id} id={photo.id}>
            <PhotoView.Thumbnail src={photo.thumb} alt={photo.alt} />
            <PhotoView.Content>
              <PhotoView.Image src={photo.full} alt={photo.alt} />
              <PhotoView.Close />
              <PhotoView.Previous />
              <PhotoView.Next />
              <div className="counter">
                <PhotoView.Current />
                <span> / </span>
                <PhotoView.Total />
              </div>
            </PhotoView.Content>
          </PhotoView>
        ))}
      </div>
    </PhotoProvider>
  );
}

Use the thumbnail as the full image

If you do not pass src to PhotoView.Image, it falls back to the thumbnail source for that view.

<PhotoProvider>
  <PhotoView.Backdrop />

  <PhotoView>
    <PhotoView.Thumbnail src="/images/product.jpg" alt="Product photo" />
    <PhotoView.Content>
      <PhotoView.Image alt="Product photo" />
      <PhotoView.Close />
    </PhotoView.Content>
  </PhotoView>
</PhotoProvider>

Open the gallery from your own controls

Use usePhotoViewGallery anywhere inside PhotoProvider when you want buttons outside the thumbnails.

import {
  PhotoProvider,
  PhotoView,
  usePhotoViewGallery,
} from "@chrisader/react-lightbox";

function GalleryToolbar() {
  const { openGallery, close, currentIndex, totalImages, isOpen } =
    usePhotoViewGallery();

  return (
    <div>
      <button type="button" onClick={() => openGallery(0)}>
        Open gallery
      </button>
      {isOpen ? (
        <button type="button" onClick={close}>
          Close
        </button>
      ) : null}
      <span>
        {isOpen ? currentIndex + 1 : 0} / {totalImages}
      </span>
    </div>
  );
}

export function Gallery() {
  return (
    <PhotoProvider>
      <PhotoView.Backdrop />

      <GalleryToolbar />

      <PhotoView id="one">
        <PhotoView.Thumbnail src="/thumb-1.jpg" alt="Photo 1" />
        <PhotoView.Content>
          <PhotoView.Image src="/image-1.jpg" alt="Photo 1" />
        </PhotoView.Content>
      </PhotoView>

      <PhotoView id="two">
        <PhotoView.Thumbnail src="/thumb-2.jpg" alt="Photo 2" />
        <PhotoView.Content>
          <PhotoView.Image src="/image-2.jpg" alt="Photo 2" />
        </PhotoView.Content>
      </PhotoView>
    </PhotoProvider>
  );
}

Replace icons and labels

Button parts accept children, and label parts accept a render function.

<PhotoView.Content>
  <PhotoView.Image src="/image-1.jpg" alt="Studio shot" />

  <PhotoView.Close>Close</PhotoView.Close>
  <PhotoView.Previous>Back</PhotoView.Previous>
  <PhotoView.Next>Forward</PhotoView.Next>

  <PhotoView.Current
    render={(current, total) => `Image ${current} of ${total}`}
  />
</PhotoView.Content>

Bring your own styles

Every visual part accepts className and style. If you want to remove the default package classes, pass resetStyles.

<PhotoView.Backdrop resetStyles className="lightbox-backdrop" />

<PhotoView.Content resetStyles className="lightbox-shell">
  <PhotoView.Image
    src="/image-1.jpg"
    alt="Campaign photo"
    resetStyles
    className="lightbox-image"
  />
  <PhotoView.Close resetStyles className="lightbox-close">
    Close
  </PhotoView.Close>
</PhotoView.Content>

Compose a new design with Tailwind

If you want a fully custom layout, use resetStyles and provide the full structure with utility classes. In this pattern, the primitives still handle state, focus, gestures, and navigation.

import { PhotoProvider, PhotoView } from "@chrisader/react-lightbox";

const photos = [
  {
    id: "01",
    thumb: "/thumb-1.jpg",
    full: "/image-1.jpg",
    alt: "Editorial portrait",
  },
  {
    id: "02",
    thumb: "/thumb-2.jpg",
    full: "/image-2.jpg",
    alt: "Studio table scene",
  },
  {
    id: "03",
    thumb: "/thumb-3.jpg",
    full: "/image-3.jpg",
    alt: "Concrete interior",
  },
];

export function TailwindGallery() {
  return (
    <PhotoProvider settings={{ loop: true }}>
      <PhotoView.Backdrop
        resetStyles
        className="fixed inset-0 z-[1001] bg-slate-950/90 backdrop-blur-md"
      />

      <div className="grid grid-cols-2 gap-4 md:grid-cols-3">
        {photos.map((photo) => (
          <PhotoView key={photo.id} id={photo.id}>
            <PhotoView.Thumbnail
              src={photo.thumb}
              alt={photo.alt}
              resetStyles
              className="aspect-[4/5] w-full rounded-2xl object-cover shadow-sm ring-1 ring-black/5 transition hover:scale-[1.01] hover:shadow-xl"
            />

            <PhotoView.Content
              resetStyles
              className="fixed inset-0 z-[1001] flex items-center justify-center p-4 sm:p-8"
            >
              <div className="relative z-10 w-full max-w-6xl">
                <div className="pointer-events-auto mb-4 flex items-center justify-between rounded-full border border-white/10 bg-white/10 px-4 py-3 text-sm text-white shadow-lg backdrop-blur">
                  <div className="flex items-center gap-2">
                    <PhotoView.Current resetStyles className="font-semibold" />
                    <span className="text-white/50">/</span>
                    <PhotoView.Total resetStyles className="text-white/70" />
                  </div>

                  <PhotoView.Close
                    resetStyles
                    className="inline-flex h-10 items-center rounded-full bg-white px-4 text-sm font-medium text-slate-900 transition hover:bg-slate-100"
                  >
                    Close
                  </PhotoView.Close>
                </div>

                <div className="relative overflow-hidden rounded-[28px] bg-black/30 shadow-2xl ring-1 ring-white/10">
                  <PhotoView.Image
                    src={photo.full}
                    alt={photo.alt}
                    resetStyles
                    className="max-h-[78vh] w-full object-contain select-none"
                  />

                  <PhotoView.Previous
                    resetStyles
                    className="pointer-events-auto absolute left-3 top-1/2 inline-flex h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur transition hover:bg-white/20 disabled:opacity-40"
                  >
                    <span aria-hidden="true">Prev</span>
                  </PhotoView.Previous>

                  <PhotoView.Next
                    resetStyles
                    className="pointer-events-auto absolute right-3 top-1/2 inline-flex h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur transition hover:bg-white/20 disabled:opacity-40"
                  >
                    <span aria-hidden="true">Next</span>
                  </PhotoView.Next>
                </div>
              </div>
            </PhotoView.Content>
          </PhotoView>
        ))}
      </div>
    </PhotoProvider>
  );
}

You can skip the package stylesheet if every visible part in your design uses resetStyles.

Provider settings

Pass settings to PhotoProvider to tune behavior:

<PhotoProvider
  settings={{
    preloadCount: 2,
    closeOnBackdropClick: true,
    enableKeyboardNav: true,
    enableTouchGestures: true,
    animationDuration: 300,
    minZoom: 1,
    maxZoom: 4,
    zoomLevels: [1, 1.5, 2.5],
    loop: true,
  }}
>
  {children}
</PhotoProvider>

Available settings:

  • preloadCount: Number of nearby images to preload.
  • closeOnBackdropClick: Close when the backdrop is clicked.
  • enableKeyboardNav: Enable Escape, Left Arrow, and Right Arrow shortcuts.
  • enableTouchGestures: Enable swipe, pinch, and double tap gestures.
  • animationDuration: Transition duration in milliseconds.
  • minZoom: Minimum zoom level.
  • maxZoom: Maximum zoom level.
  • zoomLevels: Zoom steps used when toggling zoom.
  • loop: Continue from last to first image during navigation.

Exports

Components:

  • PhotoProvider
  • PhotoView
  • PhotoView.Backdrop
  • PhotoView.Thumbnail
  • PhotoView.Content
  • PhotoView.Image
  • PhotoView.Previous
  • PhotoView.Next
  • PhotoView.Close
  • PhotoView.Current
  • PhotoView.Total
  • PhotoView.Spinner
  • PhotoView.Error

Hooks and utilities:

  • usePhotoViewGallery
  • usePhotoContext
  • useParentViewId
  • cn

Types:

  • PhotoProviderProps
  • PhotoViewProps
  • PhotoContextValue
  • View
  • PhotoProviderSettings
  • PhotoViewThumbnailProps
  • PhotoViewImageProps
  • PhotoViewContentProps
  • PhotoViewButtonProps
  • PhotoViewLabelProps
  • ZoomPanState