Swiper Thumbnail-Synced Gallery — Free JS Snippet

Swiper Thumbnail-Synced Gallery · Carousels · Plain HTML, CSS & JS · Live preview

What's included

Features

Two-way sync between the thumbnail strip and main slider with one option
Correct instance order: thumbnails first, then main with thumbs.swiper
watchSlidesProgress and the swiper-slide-thumb-active class for styling
Free-mode thumbnail strip that glides when flicked
Responsive thumbnail counts through breakpoints
Navigation arrows and keyboard control
Live counter and caption from the slideChange event
One data array generates both sets of slides

About this UI Snippet

Swiper Thumbnail-Synced Gallery — HTML, CSS & JavaScript

Screenshot of the Swiper Thumbnail-Synced Gallery snippet rendered live

The main-image-plus-thumbnails gallery is the standard layout for product pages and photo albums, and it hides a synchronisation problem. Clicking a thumbnail must move the main image. Swiping the main image must highlight the matching thumbnail. And if the highlighted thumbnail is off-screen in a long strip, the strip must scroll to reveal it. Written by hand, that is three event handlers and a lot of index arithmetic. Swiper's thumbs module does all of it from one option.

The setup has a strict order that catches people out. Two Swiper instances are involved: a thumbnails swiper with slidesPerView set to the number of thumbnails visible, and a main swiper that receives the thumbnail instance through thumbs: { swiper: thumbs }. The thumbnails instance must be created first, because the main instance needs a reference to it at construction time; reversing the order produces a thumb strip that does nothing. The thumbnails swiper also needs watchSlidesProgress: true, which is how Swiper tracks which thumbnails are visible and adds the swiper-slide-thumb-active class to the one that matches the main slide. That class is the styling hook — the CSS here uses it to lift and outline the active thumbnail and to fade the others.

freeMode on the thumbnail strip lets it be flicked and coast rather than snap slide by slide, which feels natural for a strip of small images, and breakpoints change how many thumbnails show at each width so they stay tappable on a phone. The main swiper adds navigation arrows and keyboard control. The counter and caption use the slideChange event on the main swiper, reading activeIndex, so the "3 / 8" label and the description always reflect what is on screen however the change was triggered.

The data lives in a single array of captions and gradients that generates both sets of slides, which guarantees the two lists have the same length and order — an assumption the thumbs module depends on. Photos are drawn as CSS gradients so the demo needs no image files, but in production each slide would contain an img with the full-size image and each thumb a small version.

Step by step

How to Use

  1. 1
    Click a thumbnailClick any thumbnail in the strip. The main image slides to match.
  2. 2
    Swipe the main imageDrag or use the arrow buttons. The matching thumbnail lifts and the strip scrolls to keep it visible.
  3. 3
    Flick the stripDrag the thumbnails; free mode lets the strip glide and slow to a stop.
  4. 4
    Use the keyboardPress the left and right arrow keys to move through the gallery.
  5. 5
    Read the counterThe "n / 8" badge and the caption update whichever way you change the slide.

Real-world uses

Common Use Cases

🛍️
Product detail galleries
Show a hero image with small views beneath, with clicking a thumbnail moving the main slide and swiping the main slide highlighting the thumbnail.
🌍
Photo albums and travel posts
Browse a set of photos in an album or travel post, with a free-mode thumbnail strip that glides when it is flicked.
🏠
Real estate listings
Let buyers scan room photos at a glance, using watchSlidesProgress and the swiper-slide-thumb-active class to style the current thumbnail.
🎠
Coverflow alternative
Compare with the Swiper coverflow 3D product carousel when a more theatrical presentation suits the product.
🎓
Linked component learning
Study a simple example of two components kept in sync, where the thumbnail instance must be created first and passed to the main one.

Got questions?

Frequently Asked Questions

The thumbnails Swiper must be created before the main one, because the main instance receives it via thumbs: { swiper }. Also set watchSlidesProgress: true on the thumbnails.

Swiper adds the swiper-slide-thumb-active class to the matching thumbnail. Style that class.

Yes. The thumbs module maps by index, so the two sets must have the same length and order.

Use the breakpoints option on the thumbnails swiper to change slidesPerView by width.

Listen for the main swiper's slideChange event and read main.activeIndex.

Yes. Set direction: "vertical" on the thumbnails swiper and give it a fixed height.

Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from Swiper, so in a framework project install it with npm install swiper (its React and Vue components take the same options) instead of the CDN tag, use the Swiper and SwiperSlide components, or create it in useEffect / onMounted / ngAfterViewInit, and release it with destroy() when the component unmounts.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant like Claude to add a fullscreen lightbox on click, a zoom module for pinch-to-zoom, or lazy loading for the main images.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a thumbnail-synced gallery with Swiper 11 loaded from a CDN (bundle script and CSS).

Requirements:
- Create the thumbnails Swiper first (slidesPerView 5, spaceBetween 8, freeMode, watchSlidesProgress, breakpoints), then the main Swiper with thumbs: { swiper: thumbs }, navigation and keyboard control.
- Generate both sets of slides from one array of captions and gradients.
- Style the .swiper-slide-thumb-active thumbnail (full opacity, outline, slight lift) and fade the rest.
- Show an "n / total" counter and a caption that update on the main swiper's slideChange event.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="tg-wrap">
  <div class="tg-top"><h3>Lake District weekend</h3><span class="tg-count" id="tgCount" aria-live="polite">1 / 8</span></div>
  <div class="swiper tg-main" id="tgMain">
    <div class="swiper-wrapper" id="tgMainSlides"></div>
    <div class="swiper-button-prev" aria-label="Previous photo"></div>
    <div class="swiper-button-next" aria-label="Next photo"></div>
  </div>
  <div class="swiper tg-thumbs" id="tgThumbs">
    <div class="swiper-wrapper" id="tgThumbSlides"></div>
  </div>
  <p class="tg-cap" id="tgCap" aria-live="polite"></p>
</div>