@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 #
| Key | Action |
|---|---|
| ArrowLeft | Previous image |
| ArrowRight | Next image |
| Home | First image |
| End | Last image |
| Escape | Close |
| + / = | Zoom in (requires zoom prop) |
| - | Zoom out (requires zoom prop) |
API #
| Prop | Type | Default | Description |
|---|---|---|---|
| images | LightboxImage[] | — | Image list |
| open | boolean | — | Controlled open state |
| defaultOpen | boolean | false | Uncontrolled initial open state |
| onOpenChange | (open: boolean) => void | — | Called when open state changes |
| index | number | — | Controlled current image index (0-based) |
| defaultIndex | number | 0 | Uncontrolled initial index |
| onIndexChange | (index: number) => void | — | Called when the image changes |
| loop | boolean | true | Wrap around at image boundaries |
| showThumbnails | boolean | true | Show thumbnail strip at the bottom |
| showCaption | boolean | true | Show caption below the image |
| showCounter | boolean | true | Show "N / total" counter at the top |
| showClose | boolean | true | Show the close button |
| closeOnOverlayClick | boolean | true | Close when clicking the dark overlay |
| closeOnEsc | boolean | true | Close on Escape key |
| swipe | boolean | true | Enable touch swipe navigation |
| zoom | boolean | false | Enable zoom controls and scroll-wheel zoom |
| maxZoom | number | 3 | Maximum zoom multiplier |
| onClose | () => void | — | Called when the lightbox closes |
| renderCaption | (image, index) => ReactNode | — | Custom caption renderer — overrides image.caption |
| className | string | — | Extra class on the inner container |
| style | CSSProperties | — | Inline 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;
}