Frameworks

@lightgallery/react

A native React lightGallery component. React renders the triggers and the lightbox, with no wrapper and no second runtime touching your DOM.

@lightgallery/react is a native React component, not a wrapper around the vanilla script. React renders every node, in the trigger grid and in the lightbox, so nothing else mutates your DOM and the gallery behaves like the rest of your tree: props in, callbacks out. Styling reuses the published lightgallery/css/* files, so the lightbox looks exactly like the vanilla one.

Install & styles

npm install @lightgallery/react lightgallery

Peer range: React 18 or 19 (react and react-dom).

// CSS is a consumer import, the React package ships no CSS.
import 'lightgallery/css/lightgallery.css';
// Per-plugin styles as needed:
import 'lightgallery/css/lg-thumbnail.css';
import 'lightgallery/css/lg-zoom.css';

Uncontrolled (thumbnail grid)

import { LightGallery, LightGalleryItem } from '@lightgallery/react';
import Thumbnail from '@lightgallery/react/plugins/thumbnail';
import Zoom from '@lightgallery/react/plugins/zoom';

const items = [
    {
        src: 'img/1-1600.jpg',
        thumb: 'img/1-240.jpg',
        alt: 'Mountains',
        lgSize: '1600-1067', // natural size, enables the zoom-from-origin open animation
        caption: <h4>Mountains</h4>,
    },
];

export function Gallery() {
    return (
        <LightGallery plugins={[Thumbnail, Zoom]} zoom={{ scale: 1.5 }}>
            {items.map((item) => (
                <LightGalleryItem key={item.src} item={item} href={item.src}>
                    <img src={item.thumb} alt={item.alt} />
                </LightGalleryItem>
            ))}
        </LightGallery>
    );
}

Controlled

Pass slides instead of children and the component renders no triggers; you own open and index and open the gallery from anything you like.

import { useState } from 'react';
import { LightGallery } from '@lightgallery/react';

const items = [
    { src: 'img/1-1600.jpg', thumb: 'img/1-240.jpg', alt: 'Mountains' },
    { src: 'img/2-1600.jpg', thumb: 'img/2-240.jpg', alt: 'Forest' },
];

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

    return (
        <>
            <button onClick={() => setOpen(true)}>Open gallery</button>
            <LightGallery
                slides={items}
                open={open}
                onClose={() => setOpen(false)}
                index={index}
                onIndexChange={setIndex}
            />
        </>
    );
}

Imperative

A ref exposes openGallery(index?), closeGallery(), goToSlide(index), nextSlide(), prevSlide() and refresh().

import { useRef } from 'react';
import { LightGallery, type LightGalleryRefHandle } from '@lightgallery/react';

const items = [
    { src: 'img/1-1600.jpg', thumb: 'img/1-240.jpg', alt: 'Mountains' },
    { src: 'img/2-1600.jpg', thumb: 'img/2-240.jpg', alt: 'Forest' },
];

export function Gallery() {
    const gallery = useRef<LightGalleryRefHandle>(null);

    return (
        <>
            <button onClick={() => gallery.current?.openGallery(1)}>
                Open at slide 2
            </button>
            <LightGallery ref={gallery} slides={items} />
        </>
    );
}

Settings, events and render slots

Settings are flat props with the vanilla names (mode, speed, loop, captionPosition, …); the settings reference lists every one. Per-plugin settings are a prop named after the plugin (zoom={{ scale: 1.5 }}), typed via module augmentation from each plugin entry. Lifecycle callbacks use the documented event names with an on prefix (onBeforeSlide, onAfterSlide, onSlideItemLoad, …).

The render prop swaps parts of the chrome for your own components: caption(item, index), counter(current, total), prevButton(), nextButton() and icon(name), which replaces a control icon by name. An inline gallery mounts into an element you pass as container.

SSR / Next.js

The gallery is SSR-safe by construction: every entry point imports cleanly in bare Node (ESM and CJS), and renderToString emits nothing for the gallery itself, the portal mounts client-side after hydration. Trigger children (<LightGalleryItem>) server-render as static markup, so grids are crawlable.

With the Next.js App Router the component must live in a client component:

'use client';

import { LightGallery, LightGalleryItem } from '@lightgallery/react';
// ... as above

Import the CSS in your root layout (or any server component):

// app/layout.tsx
import 'lightgallery/css/lightgallery.css';

No dynamic(() => …, { ssr: false }) wrapper is needed. The Next.js image gallery demo walks through an App Router page with next/image thumbnails.

Accessibility

The open gallery is a modal dialog (role="dialog", aria-modal, an accessible name). Focus moves into the gallery on open, Tab is trapped while it is open and focus returns to the trigger on close. Thumbnails and pager dots are keyboard-operable, and prefers-reduced-motion disables the animations. The open gallery passes axe WCAG A/AA checks in CI. The accessibility page covers the live region, the labels and the settings involved.

Keyboard bindings

KeyAction
Escclose (escKey)
← / →previous / next slide (keyPress)
Tab / Shift+Tabcycle focus within the gallery (trapFocus)
Enter / Spaceactivate focused thumbnail / pager dot

Plugins

All 14 plugins ship as subpath imports, plus the justified layout; pass them via plugins={[]} and configure each with the prop named after it. Import the matching lightgallery/css/lg-*.css where one exists.

PluginImportKey options (prop of the same name)
Thumbnail@lightgallery/react/plugins/thumbnailthumbWidth, thumbHeight, thumbMargin, animateThumb, toggleThumb
Zoom@lightgallery/react/plugins/zoomscale, actualSize, showZoomInOutIcons, infiniteZoom, enableZoomAfter
Video@lightgallery/react/plugins/videoautoplayFirstVideo, autoplayVideoOnSlide, youTubePlayerParams, vimeoPlayerParams, gotoNextSlideOnVideoEnd
Autoplay@lightgallery/react/plugins/autoplayslideShowAutoplay, slideShowInterval, progressBar, forceSlideShowAutoplay
Fullscreen@lightgallery/react/plugins/fullscreenfullScreen
Hash@lightgallery/react/plugins/hashgalleryId, customSlideName
Pager@lightgallery/react/plugins/pagerpager
Share@lightgallery/react/plugins/sharefacebook, twitter, pinterest, additionalShareOptions (typed objects)
Rotate@lightgallery/react/plugins/rotaterotateSpeed, rotateLeft/Right, flipHorizontal/Vertical
Comment@lightgallery/react/plugins/commentcommentBox, renderComments(item) render prop
MediumZoom@lightgallery/react/plugins/mediumZoommargin, backgroundColor (+ per-item lgBackgroundColor)
RelativeCaption@lightgallery/react/plugins/relativeCaptionrelativeCaption
VimeoThumbnail@lightgallery/react/plugins/vimeoThumbnailshowVimeoThumbnails, showThumbnailWithPlayButton
OriginCrop@lightgallery/react/plugins/originCroporiginCrop: flies a cropped thumbnail from its crop (zoom from origin)
Justified@lightgallery/react/plugins/justifiedNot a plugin: the <JustifiedGrid> component wraps the triggers, with rowHeight, gap, lastRow (justified layout)

Order matters for slide wrappers: put Zoom before Rotate so zoom stays the outermost transform.

Migrating from the 2.x React wrapper

Coming from lightgallery/react, the wrapper that shipped inside the vanilla 2.x package? The full list is in the migration guide; the key changes:

  • Items are data: dynamic/dynamicEl and every DOM-scraping option (selector, extraProps, exThumbImage, …) are gone, pass slides or wrap thumbnails in <LightGalleryItem item={…}>.
  • HTML-string options became typed render props: subHtml → item.caption (ReactNode) or item.captionHtml (explicit raw HTML); nextHtml/prevHtml/appendCounterTo → the render prop slots.
  • appendSubHtmlTo → captionPosition: 'bar' | 'slide' | 'outer'; addClass → className; index → controlled index/onIndexChange or defaultIndex.
  • Plugins are modules, not constructors: plugins={[Zoom]} with settings as a zoom={{ … }} prop instead of flat settings keys.
  • Events keep their documented onXxx names and payloads; updateSlides is gone (changing slides is the update). The videojs option was dropped, bring custom players through a plugin slideRenderer.

Next steps

License

Free and open source under the GPLv3, like every lightGallery package. If your project keeps its source proprietary, a commercial license covers it: same code, nothing gated. Use 0000-0000-000-0000 as a temporary licenseKey for evaluation.

Edit this page on GitHub