react ~7 KB 0 deps v0.1.0 ↗ GitHub ↗

@mshafiqyajid/react-lightbox

Headless lightbox hook and styled full-screen image viewer for React. Thumbnail strip, slide navigation with direction-aware animation, touch swipe, keyboard control (arrows, Home/End, Esc, +/−), optional zoom with scroll-wheel and +/− buttons, body scroll lock, focus trap, ARIA-compliant.

Playground #

Click any thumbnail to open. Use to navigate, Esc to close.

Props
TSX
import { LightboxStyled } from "@mshafiqyajid/react-lightbox/styled";
import "@mshafiqyajid/react-lightbox/styles.css";

<LightboxStyled
  images={images}
  open={open}
  index={index}
  onOpenChange={setOpen}
  onIndexChange={setIndex}
/>

Install #

npm install @mshafiqyajid/react-lightbox

Quick start #

import { LightboxStyled } from "@mshafiqyajid/react-lightbox/styled";
import "@mshafiqyajid/react-lightbox/styles.css";
import { useState } from "react";

const images = [
  { src: "/photo-1.jpg", alt: "Mountain landscape", caption: "Swiss Alps, 2024" },
  { src: "/photo-2.jpg", alt: "Ocean sunset",       caption: "Malibu, California" },
  { src: "/photo-3.jpg", alt: "City skyline",        caption: "Tokyo at night" },
];

export function Gallery() {
  const [open, setOpen] = useState(false);
  const [index, setIndex] = useState(0);

  return (
    <>
      <div style={{ display: "flex", gap: "0.5rem" }}>
        {images.map((img, i) => (
          <img
            key={i}
            src={img.src}
            alt={img.alt}
            style={{ width: 120, height: 80, objectFit: "cover", cursor: "pointer" }}
            onClick={() => { setIndex(i); setOpen(true); }}
          />
        ))}
      </div>

      <LightboxStyled
        images={images}
        open={open}
        index={index}
        onOpenChange={setOpen}
        onIndexChange={setIndex}
      />
    </>
  );
}

With zoom #

Enable zoom to let users pinch, scroll-wheel, or press +/ to zoom the active image. Pan while zoomed by dragging.

<LightboxStyled
  images={images}
  open={open}
  index={index}
  onOpenChange={setOpen}
  onIndexChange={setIndex}
  zoom
  maxZoom={4}
/>

Custom caption #

Override the caption renderer for rich content.

<LightboxStyled
  images={images}
  open={open}
  index={index}
  onOpenChange={setOpen}
  onIndexChange={setIndex}
  renderCaption={(img, i) => (
    <span>
      <strong>{img.alt}</strong> — {img.caption} ({i + 1}/{images.length})
    </span>
  )}
/>

Headless hook #

import { useLightbox } from "@mshafiqyajid/react-lightbox";

const {
  isOpen,
  open,
  close,
  index,
  setIndex,
  prev,
  next,
  zoomLevel,
  setZoomLevel,
  lightboxProps,   // role, aria-modal, aria-label
  overlayProps,    // data-state
} = useLightbox({
  total: images.length,
  loop: true,
  zoom: true,
  maxZoom: 3,
});

// Open at a specific index
<button onClick={() => open(2)}>Open 3rd photo</button>

LightboxImage type #

interface LightboxImage {
  src: string;      // Full-size image URL
  alt: string;      // Alt text (required for accessibility)
  caption?: string; // Caption displayed below the image
  thumb?: string;   // Thumbnail URL — falls back to src
  width?: number;   // Native width (improves CLS)
  height?: number;  // Native height
}

Keyboard #

KeyAction
ArrowLeftPrevious image
ArrowRightNext image
HomeFirst image
EndLast image
EscapeClose
+ / =Zoom in (requires zoom prop)
-Zoom out (requires zoom prop)

API #

PropTypeDefaultDescription
imagesLightboxImage[]Image list
openbooleanControlled open state
defaultOpenbooleanfalseUncontrolled initial open state
onOpenChange(open: boolean) => voidCalled when open state changes
indexnumberControlled current image index (0-based)
defaultIndexnumber0Uncontrolled initial index
onIndexChange(index: number) => voidCalled when the image changes
loopbooleantrueWrap around at image boundaries
showThumbnailsbooleantrueShow thumbnail strip at the bottom
showCaptionbooleantrueShow caption below the image
showCounterbooleantrueShow "N / total" counter at the top
showClosebooleantrueShow the close button
closeOnOverlayClickbooleantrueClose when clicking the dark overlay
closeOnEscbooleantrueClose on Escape key
swipebooleantrueEnable touch swipe navigation
zoombooleanfalseEnable zoom controls and scroll-wheel zoom
maxZoomnumber3Maximum zoom multiplier
onClose() => voidCalled when the lightbox closes
renderCaption(image, index) => ReactNodeCustom caption renderer — overrides image.caption
classNamestringExtra class on the inner container
styleCSSPropertiesInline style on the inner container

CSS variables #

:root {
  --rlbx-overlay-bg: rgba(0, 0, 0, 0.95);
  --rlbx-nav-size: 3rem;
  --rlbx-nav-bg: rgba(255, 255, 255, 0.08);
  --rlbx-nav-bg-hover: rgba(255, 255, 255, 0.18);
  --rlbx-thumb-size: 4rem;
  --rlbx-thumb-ring: #fff;
  --rlbx-caption-color: rgba(255, 255, 255, 0.85);
  --rlbx-duration-overlay: 220ms;
  --rlbx-duration-image: 200ms;
}
Edit this page on GitHub