Changelog

All notable changes to lightGallery. The format follows Keep a Changelog; versions follow Semantic Versioning.

3.0.0 (2026-10-08)

Published under the latest tag for every package: lightgallery, @lightgallery/headless, @lightgallery/react, @lightgallery/vue and @lightgallery/angular, all at 3.0.0. The 3.0.0 betas were published under next; @lightgallery/headless@3.0.0-beta.1 shipped without its build output and is deprecated.

Version 3 is a complete rewrite of lightGallery around a shared, framework-free core, with native packages for React, Vue and Angular and performance and experience improvements throughout. The vanilla API is unchanged, so a 2.x gallery keeps working.

Packages

  • @lightgallery/headless, new. The gallery’s logic without a DOM: state machine, settings resolution, gesture and zoom math, thumbnail windowing, URL drivers, video URL helpers. Every package below builds on it.
  • @lightgallery/react, new native React package. Components, render slots, callbacks and an imperative handle; React owns every DOM node. Replaces the lightgallery/react wrapper.
  • @lightgallery/vue, new native Vue 3 package. v-model for the open state, slots, Teleport. Replaces the lightgallery/vue wrapper.
  • @lightgallery/angular, new native Angular package. Standalone components, signal inputs, zoneless change detection, CDK overlay. Replaces the lightgallery/angular wrapper.
  • lightgallery, the vanilla package, modernized build with an exports map; plugins remain separate entries under lightgallery/plugins/*.

Added

  • Justified layout plugin: row-justified trigger grids from the gallery itself (lightgallery/plugins/justified, and grid components in each framework package). The grid is never seen unorganised: thumbnails stay out of flow until the layout has positioned them, each box then shows as a placeholder and its thumbnail fades in once loaded. justifiedReveal picks whether rows fill in top to bottom or each thumbnail appears on its own. Ship class="lg-justified" in the container markup so the hiding also covers the window before the script runs.
  • Origin crop plugin (lightgallery/plugins/originCrop, and in each framework package): the zoom-from-origin flight from a cropped thumbnail. A tile cropped with object-fit: cover or background-size: cover shows a window of the photo, and the built-in flight squashes the whole photo into it; with the plugin that window grows from the tile at a uniform scale while the rest of the photo is revealed around it, and the close flies back the same way. It reads the thumbnail’s computed fit, so no markup or stylesheet is needed.
  • Toolbar overflow: the toolbar stays on one row. When its buttons do not fit beside the counter, the lowest-priority ones move into a “More options” menu (toolbarOverflow, on by default), labelled by strings.moreOptions and drawn with the replaceable more icon. Touch devices also leave out the zoom in, zoom out and actual size buttons, which repeat pinch and double-tap (showGestureButtons, turned off through mobileSettings).
  • Zoom from origin in dynamic mode: a dynamic gallery now flies open from the element passed to openGallery(index, element), and closes with the centre fade, like the framework packages. The media size comes from the new lgSize item field (data-lg-size on markup items fills the same field), or from a data-lg-size attribute on that element. Dynamic galleries used to force zoomFromOrigin off.
  • Virtualization: virtualization setting keeps a window of slides and thumbnails mounted for very large galleries.
  • Video facades: video slides render a poster and load the player on first play; videoFacade and youTubeNoCookie settings.
  • Thumbnail scrubbing: scrubThumbnails turns the thumbnail strip into a scrubber that drives the gallery through the release glide.
  • Custom icons: icons setting (React render.icon, Vue :icons, Angular lgIcon template) replaces any control icon by name. Built-in icons are inline SVG; the icon font is gone.
  • Localization and RTL: every UI string is a setting (strings, merged per key over the defaults); direction setting with an opt-in lg-rtl.css layer that mirrors navigation, swipe and chrome.
  • Web Share: the share button prefers the device’s native share sheet (preferNativeShare) with the social links as fallback; X replaces the Twitter target.
  • Hash drivers: hashDriver chooses between the hash, history and Navigation API drivers behind one interface.
  • Responsive loading: size ladders and srcset/sizes selection for the lightbox image, with decode-gated slide completion.
  • Accessibility: dialog semantics, focus trapping and restoration, labelled controls from strings, a polite live region announcing slide changes (ariaAnnouncements), reduced-motion support.
  • Gesture physics: releases run a velocity-seeded damped spring with momentum projection; boundary friction instead of hard clamps; pinch-to-close (pinchToClose); a flickVelocity setting.
  • Public types: lightgallery exports the LightGallery instance type (for a variable or a plugin’s core, without ReturnType), LightGallerySettings, GalleryItem and every event detail type (InitDetail, BeforeSlideDetail, …) from the package entry: import type { LightGallerySettings } from 'lightgallery'.

Changed

  • Icons are inline SVG; the lg icon font and its files are removed.
  • Plugin labels (aria-labels and titles) come from the core strings setting; the per-plugin *PluginStrings settings are deprecated aliases.
  • The share plugin’s Twitter target is now X: the shareX icon name, and the X mark in place of the old bird.
  • lightgallery declares Node 18 as its minimum in engines, matching the other packages.
  • In the React, Vue and Angular packages the zoom plugin’s actual-size button now follows the vanilla behaviour: it carries the zoomIn and zoomOut icons and switches between the actualSizeIcons classes with the zoom state, while the zoom in and zoom out buttons keep their fixed lg-zoom-in and lg-zoom-out classes. The button is 24px like every other toolbar icon in all packages. The actualSize icon name is no longer used by any default rendering.
  • In the React, Vue and Angular packages init is always the first event: a gallery mounted already open fires init, then beforeOpen and afterOpen, as the vanilla gallery does (the betas fired beforeOpen first).

Fixed

Long-standing bugs, nearly all of them present in 2.x:

  • The vanilla focus trap counts every focusable element in the dialog, including tabindex ones, so Tab cannot leave through plugin content.

  • A thumbnail click that moved by a few pixels started a strip drag: with scrubThumbnails on, the release glide kept the scrub session alive and the click’s own slide change did not re-centre the strip, and on a long strip the wobble itself navigated (one pixel is several slides). A press that moves less than thumbnailSwipeThreshold is a click in every package: it neither scrubs nor glides. In a windowed vanilla strip (virtualization.thumbs) the previous thumbnail also kept its active mark after a slide change, and a wobble toward either end of the strip rebuilt it under the pointer (the elastic overshoot read as travel past the rendered window), which swallowed the click; the frameworks committed a translate mid-drag for the same reason.

  • Closing a zoomed image in the React, Vue and Angular packages left it enlarged: the close flight shrank the slide around the zoom wrappers, so the image flew back to its trigger at the zoomed scale, covering the thumbnails and the page around it. The zoom now resets in the same frame the close starts, as in vanilla, and the image lands on its thumbnail.

  • The slideshow ran on a fixed interval, so on a slow connection it advanced past images that had not loaded yet. The countdown (and the progress bar) now starts once the slide on screen has loaded; a slide that fails to load settles the same way, so a broken image does not stall the show.

  • In the React, Vue and Angular packages a slide that left the mounted pool kept its loaded flag, so returning to it in a large gallery showed no loader while the image downloaded again.

  • The React <LightGalleryItem> and an Angular [lgGalleryItem] anchor rendered without an href unless you passed one, so keyboard users could not tab to the thumbnails and the server-rendered markup linked nowhere. An anchor trigger now links to item.src by default, as the Vue <LgItem> already did; an explicit href still wins.

  • Slide transitions crossfaded for the whole slide instead of the brief fade the modes ask for, so the outgoing image ghosted across the transition. Transform and opacity now keep their own durations, and the modes whose effect is the fade keep the long one.

  • Zoom clamped a repositioning zoom against a wider window than its own release settle uses. Zooming near an edge parked the image where the next tap immediately moved it away from.

  • Rotating an image and then zooming divided by a zero previous scale, handing the pan origin NaN and voiding the transform, so panning silently stopped tracking.

  • Medium zoom closed the gallery on every click, toolbar buttons included, so rotating or sharing dismissed the image. Toolbar clicks now leave it open; a click on the slide or backdrop still closes.

  • The thumbnail strip animated its first positioning, so opening the gallery from a thumbnail far along the strip slid it across while the image was still flying in. That first positioning is now instant.

  • The thumbnail strip is built when the gallery initialises, and its images loaded eagerly, so every page with a closed gallery downloaded all of its strip thumbnails at load. Strip images are now lazy; nothing is fetched until the gallery opens, and thumbs far along the strip load as they scroll into view.

  • The share dropdown hung from the toolbar’s corner instead of the share button, so on narrow screens it opened nowhere near the control (and its arrow pointed at the wrong one). Button and menu now share a wrapper that anchors the menu under the button, which also gets the list out of the <button> it was nested in. Closing the gallery with the dropdown open left it open on the next open; it now closes with the gallery, and the share button’s aria-expanded tracks it.

  • The share dropdown stayed open while the other toolbar buttons and the arrows were used: its dimming overlay sat below the toolbar, so only a click on the slide dismissed it, and Escape closed the whole gallery instead. The dropdown now dismisses like the More options menu: on a press anywhere outside it, on Escape (the gallery stays open) and when focus tabs out of it. A dismissing tap on the backdrop no longer also closes the gallery, for the More options menu as well.

Removed

  • The lightgallery/react, lightgallery/vue and lightgallery/angular wrappers, replaced by the native packages above.
  • The share dropdown’s .lg-dropdown-overlay element.
  • The supportLegacyBrowser setting and the responsive-image polyfill hook behind it, along with the CustomEvent and Element.matches polyfills and the old-browser scroll fallbacks. Galleries with srcset or <picture> sources no longer log a warning asking for a polyfill.
  • Vendor prefixes in the stylesheets. The CSS carried -webkit-, -moz-, -ms- and -o- copies of every transform, transition, animation and grab cursor; every supported browser reads the standard properties, so the bundle is about a quarter smaller and Angular’s critical-CSS step no longer warns about the prefixed keyframes. -webkit-backface-visibility stays for iOS Safari before 15.4. The SCSS mixins keep their names and now emit the standard property, so stylesheets that include them still compile.
  • The moz and ms fullscreen API fallbacks in the vanilla fullscreen plugin. The webkit fallback stays for Safari before 16.4, as in the framework packages.

See the migration guide for the upgrade steps.

2.9.0 and earlier

Release notes for the 2.x line are on GitHub releases.