Lightbox with Thumbnails
Large image with, right below it, a strip of every thumbnail: jump straight to the photo you want, or step through with ‹ ›, ← → and a finger.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Gallery has 44 effects, including 6 free. Explore the category →
3 usage examples



How it works
You only write the gallery once. On load, the script walks through the .tlb-thumb buttons and builds, inside .tlb-strip, a small button for each, with the same image and the same alt. The strip therefore always follows the gallery, however many photos it holds.
The miniature of the image on display is highlighted (full opacity, white border, 10 % larger) and carries aria-current="true"; the others stay half transparent. When the strip is wider than 80 % of the screen it scrolls horizontally and recentres the active miniature on every change.
The rest comes from the arrow viewer: looping ‹ ›, ← → keys, a horizontal drag of more than 50 px on the large image, Escape / ✕ / background click to close. The large image is limited to 80 % of the width and to the screen height minus 110 px, so the strip stays visible even on a phone held sideways.
Accessibility
- Two ways to choose: strip miniatures are buttons named by their photo's
alt, grouped in arole="group"called “Thumbnails”; the active one is flagged witharia-current. - Keyboard: Tab goes through ✕, ‹, ›, then each miniature; ← → change image from any button in the viewer; Escape closes.
- Dialog:
showModal(), named “Image viewer”, page behind inert, focus returned to the original thumbnail on closing. - Reduced motion: opening without fade or zoom, miniatures without transition; navigation and swiping remain available.
Browser compatibility
Base shared by the site's viewers (modal <dialog>: Firefox 98, Safari 15.4; aspect-ratio: Chrome 88). justify-content: safe center keeps an over-long strip from being cut off on the left; where safe is not understood, the preceding center declaration applies. Tried in Chromium 151, Firefox 153 and WebKit 26.5: strip built in each copy, click on the 4th miniature, touch at 390 px.
Without JavaScript the strip stays empty and nothing opens: only the gallery shows.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0175-root">
<div class="tlb-grid">
<button type="button" class="tlb-thumb" aria-haspopup="dialog"><img src="photo.jpg" alt="Violet to cyan gradient" width="600" height="400"></button>
<button type="button" class="tlb-thumb" aria-haspopup="dialog"><img src="photo.jpg" alt="Cyan to emerald gradient" width="600" height="400"></button>
<button type="button" class="tlb-thumb" aria-haspopup="dialog"><img src="photo.jpg" alt="Fuchsia to rose gradient" width="600" height="400"></button>
<button type="button" class="tlb-thumb" aria-haspopup="dialog"><img src="photo.jpg" alt="Pink to violet gradient" width="600" height="400"></button>
</div>
<dialog class="tlb-viewer" aria-label="Image viewer">
<div class="tlb-content">
<button type="button" class="tlb-close" aria-label="Close"><svg>…</svg></button>
<button type="button" class="tlb-nav tlb-prev" aria-label="Previous image"><svg>…</svg></button>
<img class="tlb-image" alt="">
<button type="button" class="tlb-nav tlb-next" aria-label="Next image"><svg>…</svg></button>
<div class="tlb-strip" role="group" aria-label="Thumbnails"></div>
</div>
</dialog>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Gallery thumbnails (CSS — --tlb-thumb) |
120px | Width of the buttons on the page (3:2 format). |
Strip miniatures (CSS — .tlb-strip-btn) |
60 × 40 px | Fixed size in the CSS; make them bigger and reduce the 110 px left under the large image by the same amount. |
Large image height (CSS — max-height of .tlb-image) |
calc(80vh - 110px) |
The remaining space is the strip's; adjust it if you change the miniature size. |
Overlay (CSS — --tlb-backdrop) |
rgba(0, 0, 0, 0.9) |
Viewer background. |
Corners (CSS — --tlb-radius, --tlb-radius-lg) |
0.5rem, 1rem | Page thumbnails; large image and strip background. |
Large version (HTML — data-full) |
absent | The large view loads that URL; the strip keeps the light thumbnail. |
Swipe threshold (JS — 50) |
50 px | Horizontal drag needed on the large image to change photo. |
FAQ
<div class="tlb-strip"> empty: the script fills it from the gallery. It only sees the photos present when it runs: for a gallery built later, insert the whole root then run the script again (roots already wired are skipped).display: none); it is still counted, present in the strip and reachable with the arrows.