liftbox

liftbox vs PhotoSwipe

How liftbox compares with PhotoSwipe and react-photoswipe-gallery in React, and how to move a gallery from one to the other.

PhotoSwipe is the lightbox most people already know, and its touch gestures are still some of the best around. It isn't built for React, though: React apps reach it through the community wrapper react-photoswipe-gallery, and that's the setup compared here, PhotoSwipe 5.4.4 with react-photoswipe-gallery 4.1.2, checked on November 4, 2026. The full comparison covers the other libraries.

What they share

Both are MIT, so a closed-source product pays nothing. Both open the image out of the thumbnail you clicked, cropped with object-fit: cover or not, and both handle pinch, double tap, wheel zoom, swipes, the keyboard and reduced motion.

Where they differ

liftboxPhotoSwipe with react-photoswipe-gallery
Built forReact 18 and 19Any framework, React through a community wrapper
Image sizewidth and height optional, measured on load when missingRequired for every image before it opens
Native video, YouTube, VimeoBuilt inA separate video plugin, in development
Thumbnail stripBuilt inNo
SlideshowBuilt inNo
FullscreenBuilt inA recipe in the docs
Share and rotationBuilt inNo
Deep links (URL hash)useLightboxHashIn the React wrapper
GridsMasonry, justified, uniform and a filterNo
Dialog with aria-modal, slide announcementsYesDialog without aria-modal, no announcements
JS size, min and brotli13.12 kB with everything above17.49 kB
Last release2026PhotoSwipe 5.4.4, May 2024

When to pick PhotoSwipe

Pick PhotoSwipe if your site isn't only React, or you want the same lightbox in Vue, Svelte and plain HTML. It has around 25,000 GitHub stars and years in production, which counts for a lot when you can't afford surprises.

It's also the simpler choice when you already store every image's width and height and need images only, with no video, thumbnail strip or slideshow.

Gallery and Item turn into a slide list plus one controlled Lightbox. original becomes src, thumbnail keeps its name, and width and height carry over as numbers (or drop them, and liftbox measures each image when it loads).

- import { Gallery, Item } from "react-photoswipe-gallery";
- import "photoswipe/dist/photoswipe.css";
+ import { useRef, useState } from "react";
+ import { Lightbox, type Slide } from "liftbox";
+ import "liftbox/styles.css";
const slides: Slide[] = photos.map((photo) => ({
  src: photo.original,
  thumbnail: photo.thumbnail,
  width: photo.width,
  height: photo.height,
  alt: photo.alt,
  title: photo.caption,
}));

export function Photos() {
  const [index, setIndex] = useState(-1);
  const thumbnails = useRef<(HTMLButtonElement | null)[]>([]);
  return (
    <>
      {slides.map((slide, i) => (
        <button
          key={slide.src}
          ref={(el) => {
            thumbnails.current[i] = el;
          }}
          onClick={() => setIndex(i)}
          aria-label={`Open ${slide.alt}`}
        >
          <img src={slide.thumbnail} alt="" />
        </button>
      ))}
      <Lightbox
        slides={slides}
        open={index >= 0}
        index={Math.max(index, 0)}
        onIndexChange={setIndex}
        onClose={() => setIndex(-1)}
        getThumbnail={(i) => thumbnails.current[i]}
      />
    </>
  );
}

Rather not write the thumbnail buttons yourself? Grids renders masonry, justified or uniform layouts and wires the thumbnails for you.

Edit this page on GitHub

On this page