Embla Carousel Thumbnail Gallery — Free Synced Thumbnails Snippet

Embla Carousel Thumbnail Gallery · Carousels · Plain HTML, CSS & JS · Live preview

What's included

Features

Synced carousels
Main carousel drives the thumbnail strip.
Drag-free thumbnails
Momentum scrolling for the strip.
Click vs drag handled
Embla v8 swallows the click that ends a drag.
Active thumbnail
Marked with aria-current and a highlight ring.
Auto-reveal
The strip scrolls to keep the active thumbnail in view.
Live counter
"3 / 10" announced politely.
Keyboard support
Arrow keys step the main carousel.
Responsive thumbnails
Size changes with a CSS media query.

About this UI Snippet

Embla Carousel Thumbnails — Two Carousels, One Source of Truth

Screenshot of the Embla Carousel Thumbnail Gallery snippet rendered live

A thumbnail gallery is really two carousels that have to agree: a large one that shows the current photo and a small strip of previews that both shows where you are and lets you jump. The usual source of bugs is letting both control each other. This snippet makes the main carousel the single source of truth.

Two instances, different behaviour

The main carousel shows one full-width slide at a time. The thumbnail strip is its own Embla instance with dragFree: true, so it glides with momentum like a native scrolling list instead of snapping thumbnail by thumbnail, and containScroll: 'keepSnaps' keeps each thumbnail individually addressable.

One-way flow

Thumbnails only ever call main.scrollTo(i). The main carousel's select event then updates everything else: it scrolls the strip to the active thumbnail with thumbs.scrollTo(i), marks it with aria-current, and updates the "3 / 10" counter. Because data flows one way, swiping the main photo, clicking a thumbnail and pressing an arrow key all end in the same state.

Click versus drag

Flicking the thumbnail strip ends with a pointer-up on some thumbnail, which the browser reports as a click. Embla v8 handles this itself: it listens for click in the capture phase on its container and stops the click that ends a drag, so the thumbnail handlers never see it and a flick doesn't jump the main photo. (Embla v7 exposed this as clickAllowed(), which older tutorials still use; it no longer exists in v8.)

Accessible thumbnails

Each thumbnail is a real <button> with a label; its image has empty alt text because the button already describes it. The counter is a polite live region.

Responsive strip

Thumbnails are 22% wide on phones and 16% on larger screens, changed purely in CSS.

Step by step

How to Use

  1. 1
    Load EmblaInclude embla-carousel.umd.js from the CDN.
  2. 2
    Paste the snippetA main photo carousel renders above a thumbnail strip.
  3. 3
    Click a thumbnailThe main carousel scrolls to that photo.
  4. 4
    Swipe or use arrow keysThe strip follows and highlights the active thumbnail.
  5. 5
    Use your imagesReplace IDS, or render your own slides and thumbnails in the same order.

Real-world uses

Common Use Cases

Online store photo viewer
Build the classic e-commerce gallery: a large main photo with a thumbnail strip beneath it. The main carousel drives the strip, so the two can never fall out of sync.
Property and vehicle listings
Let buyers jump straight to the kitchen, the engine bay or the interior with one tap on a thumbnail. The drag-free strip scrolls with momentum when there are many photos.
Portfolio case-study images
Browse a project's screens in order with a live slide counter showing the position. The active thumbnail carries aria-current and a highlight ring for keyboard and screen reader users.
Recipe and tutorial photo sets
Step through process photos with thumbnails as a visual table of contents. Click-versus-drag detection stops a swipe on the strip from accidentally jumping to the wrong picture.
Understanding two-way carousel sync
Learn why one carousel should be the single source of truth. Letting both carousels control each other creates feedback loops, which is the usual bug this pattern avoids.
Related: Swiper Thumbnail-Synced Gallery
Compare with the Swiper approach: Swiper Thumbnail-Synced Gallery.
Related: Embla Arrows, Dots and Keyboard

Got questions?

Frequently Asked Questions

Create two Embla instances. Thumbnail clicks call main.scrollTo(index). In the main carousel's select event, call thumbs.scrollTo(main.selectedScrollSnap()) and update the active thumbnail. Keeping updates one-directional avoids loops.

Not in Embla v8. It adds a capture-phase click listener to its container that stops the click ending a drag. Code written for v7 often calls clickAllowed(), which was removed in v8 and throws if called.

With dragFree, Embla lets the carousel glide with momentum and stop anywhere instead of snapping to the nearest slide. It suits thumbnail strips and long rows.

It keeps every slide's own snap point instead of trimming snaps at the ends. That way scrollTo(i) works for every thumbnail index, including the first and last few.

Use button elements with an aria-label for each thumbnail, mark the active one with aria-current, give decorative thumbnail images empty alt text, and announce the current position in a live region.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Give this snippet to an AI assistant like Claude and ask it to explain the one-way data flow between the two carousels and how Embla v8 stops a drag from registering as a click. Ask it to add zoom-on-click with a lightbox, vertical thumbnails beside the main image on desktop, or lazy loading so only nearby photos download. It can also help wire the gallery to product variants, swapping the image set when a colour is chosen.

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 synced thumbnail photo gallery with Embla Carousel v8 (loaded from a CDN as UMD) in plain HTML, CSS and JavaScript.

Requirements:
- Render ten photos into a main carousel that shows one full-width slide at a time, and ten matching square thumbnails into a second carousel below it.
- Make the thumbnail carousel drag-free with snaps kept for every thumbnail.
- Thumbnails are buttons with aria-labels; clicking one scrolls the main carousel to that photo, relying on Embla v8 to suppress the click that ends a drag of the strip.
- Treat the main carousel as the source of truth: on its select event, scroll the thumbnail strip to the active thumbnail, mark it with aria-current and a highlight ring, and update a "3 / 10" counter in a polite live region.
- Support left and right arrow keys.
- Show about four thumbnails on phones and six on wider screens using CSS only.

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="etg">
  <div class="etg-main">
    <div class="etg-viewport" id="etgMain">
      <div class="etg-container" id="etgSlides"></div>
    </div>
    <div class="etg-count" id="etgCount" aria-live="polite"></div>
  </div>
  <div class="etg-thumbs">
    <div class="etg-viewport" id="etgThumbs">
      <div class="etg-container" id="etgThumbList"></div>
    </div>
  </div>
</div>