A lightbox that flies out of your thumbnails.

Accessible React gallery viewer with zoom, video, slideshow, deep links, sharing and optional grids. Every string, icon and color is yours to replace. MIT licensed, so you can ship it in a closed-source product without paying for anything, and it has no runtime dependencies.

npm install liftbox
lightbox, min + brotli, measured
13.16 kB
free in closed-source projects, 0 runtime dependencies
MIT
SSR safe, App Router ready
React 18 and 19
focus trap, live region, reduced motion
Screen readers

Default look

The built-in theme, driven by the options panel. The open photo is kept in the URL as #galeria-3, so you can share it, and the back button closes the viewer.

Transition
Viewer
3×
Behaviour
2 each side
50px

Customised

Portuguese labels, replaced arrow icons, a custom caption, an extra toolbar link and a brand theme set only through --eg-* variables.

Live demo. Click a thumbnail to open it.

Video and slideshow

A native video with captions, an iframe slide and images. Press play in the toolbar to run the slideshow. It pauses on manual navigation, while the tab is hidden and on video slides until the video ends.

Slideshow
3s

Interaction polish

Flip, share and every other button: on a narrow screen the ones that do not fit move into a More menu, zoom first. Pager dots, controls that fade out when idle, right-to-left and pinch to close are each one prop.

Polish

Grids and filter

Justified rows, masonry columns or uniform cells, with a category filter. The grids work without the lightbox and the lightbox works without them. Here they share the filtered list, so indices always match.

12 images

Compared with other lightboxes

lightGallery asks for a paid license once your code is closed source, and yet-another-react-lightbox needs a plugin per feature plus react-photo-album for grids. Here it is one MIT package.

Usage

Keep the lightbox mounted next to your grid and drive it with an index. The quick start walks through it.

import { useRef } from "react";
import { Lightbox, useLightboxHash, type Slide } from "liftbox";
import "liftbox/styles.css";

export function Gallery({ slides }: { slides: Slide[] }) {
  const thumbnails = useRef<(HTMLButtonElement | null)[]>([]);
  const [index, setIndex] = useLightboxHash("galeria", { count: slides.length });

  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]}
      />
    </>
  );
}