Add left/right navigation arrows to WooCommerce product gallery

I’m using WooCommerce with the following theme: CUBE NATURE Pro – Bikys

On the product detail page, the default product gallery only allows switching product images by clicking the thumbnail images below the main image.

I want to enhance the user experience by adding left and right arrow buttons to the sides of the main images, so that visitors can navigate through the gallery more easily.

I’ve implemented the feature using the code below:

    <style>
.woo-gallery-nav {position: absolute;top: 50%;z-index: 10;transform: translateY(-50%);background: rgba(0,0,0,0.5);color: #ffdb00;font-size: 30px;width: 30px;height: 30px;line-height: 30px;text-align: center;cursor: pointer;user-select: none;border-radius: 15px;transition: all 0.3s ease;}
.woo-gallery-nav:hover {background: rgba(0,0,0,0.8);}
.woo-gallery-nav.prev {left: 0px;}
.woo-gallery-nav.next {right: 0px;}
.flex-control-thumbs li {cursor: pointer;transition: opacity 0.3s ease;}
.flex-control-thumbs li.flex-active {opacity: 0.7;border: 2px solid #333;}
.woocommerce-product-gallery__image {transition: opacity 0.5s ease;}
</style>
<script>
document.addEventListener("DOMContentLoaded", function () {
  function initializeGalleryNavigation() {
    const galleryWrapper = document.querySelector(".woocommerce-product-gallery__wrapper");
    const slides = Array.from(galleryWrapper.querySelectorAll('.woocommerce-product-gallery__image'));
    const thumbs = Array.from(document.querySelectorAll(".flex-control-thumbs li"));
    if (!slides.length || !thumbs.length) {
      setTimeout(initializeGalleryNavigation, 100);
      return;
    }
    let currentIndex = slides.findIndex(slide => slide.classList.contains('flex-active-slide'));
    if (currentIndex === -1) currentIndex = 0;
    const prevBtn = document.createElement("div");
    prevBtn.className = "woo-gallery-nav prev";
    prevBtn.innerHTML = "‹";
    const nextBtn = document.createElement("div");
    nextBtn.className = "woo-gallery-nav next";
    nextBtn.innerHTML = "›";
    galleryWrapper.parentNode.appendChild(prevBtn);
    galleryWrapper.parentNode.appendChild(nextBtn);
    function updateGallery(index) {
      if (index < 0) index = slides.length - 1;
      if (index >= slides.length) index = 0;
      thumbs[index].querySelector('img').click();
      currentIndex = index;
    }
    prevBtn.addEventListener("click", () => updateGallery(currentIndex - 1));
    nextBtn.addEventListener("click", () => updateGallery(currentIndex + 1));
    document.addEventListener("keydown", (e) => {
      if (e.key === "ArrowLeft") updateGallery(currentIndex - 1);
      if (e.key === "ArrowRight") updateGallery(currentIndex + 1);
    });
    thumbs.forEach((thumb, i) => {
      thumb.addEventListener("click", () => {
        currentIndex = i;
      });
    });
  }
  initializeGalleryNavigation();
});
</script>

and it’s somewhat functional. However, I’ve encountered a small bug on mobile devices: When using the arrow buttons to switch images, the logic sometimes conflicts with the default thumbnail click behavior. For example, clicking the arrows may occasionally cause the thumbnails to stop working, or clicking a thumbnail may cause the arrow buttons to stop functioning correctly.

Is this an issue with how I’m handling the event bindings or gallery state? Do I need to further optimize my JavaScript to handle mobile interactions more gracefully?

Thanks in advance!

Yes, the issue you’re encountering stems from how the gallery state is managed and how event handlers interact with the FlexSlider instance (which WooCommerce uses by default). The problem likely occurs because:

  • The arrow buttons trigger a thumbnail image click, which may conflict with FlexSlider’s internal navigation state.
  • You’re manually updating currentIndex, but it can fall out of sync with FlexSlider’s actual current slide, especially on touch interactions or rapid changes.
  • On mobile, FlexSlider may apply touch or swipe behaviors that also change the slide, without your script being aware of the change.

Problems Identified:

  1. Race Condition Between Events: Clicking arrows or thumbnails rapidly may cause your currentIndex to become desynchronized from the actual active slide.
  2. Manual State Tracking: You’re keeping your own currentIndex, but FlexSlider also tracks the current slide. If you don’t listen to its events (start, after, etc.), you risk a mismatch.
  3. Direct DOM Manipulation: Using thumb.querySelector('img').click() may not be the most robust or consistent way to trigger a slide change.

Solution Approach

You can sync with FlexSlider’s internal state to avoid these issues and simplify the logic.

Here’s a more reliable approach:

1. Use FlexSlider API instead of simulating thumbnail clicks

WooCommerce uses $.flexslider() on .woocommerce-product-gallery. So you can access the FlexSlider instance and call .flexslider("next"), .flexslider("prev"), or .flexslider("animateSlide", index) directly.

2. Sync with FlexSlider events

Listen to after or start events to update currentIndex.

Revised Script (Mobile-Friendly and Sync-Safe)

<script>
document.addEventListener("DOMContentLoaded", function () {
  function initializeGalleryNavigation() {
    const $gallery = jQuery(".woocommerce-product-gallery");

    // Ensure FlexSlider is loaded
    if (!$gallery.length || !$gallery.data("flexslider")) {
      setTimeout(initializeGalleryNavigation, 100);
      return;
    }

    const slider = $gallery.data("flexslider");

    const prevBtn = document.createElement("div");
    prevBtn.className = "woo-gallery-nav prev";
    prevBtn.innerHTML = "‹";

    const nextBtn = document.createElement("div");
    nextBtn.className = "woo-gallery-nav next";
    nextBtn.innerHTML = "›";

    const galleryWrapper = document.querySelector(".woocommerce-product-gallery__wrapper");
    galleryWrapper.parentNode.appendChild(prevBtn);
    galleryWrapper.parentNode.appendChild(nextBtn);

    // Navigation
    prevBtn.addEventListener("click", () => slider.flexslider("prev"));
    nextBtn.addEventListener("click", () => slider.flexslider("next"));

    // Keyboard navigation
    document.addEventListener("keydown", (e) => {
      if (e.key === "ArrowLeft") slider.flexslider("prev");
      if (e.key === "ArrowRight") slider.flexslider("next");
    });

    // Optional: Update any custom index-tracking if needed
    slider.vars.after = function (sliderInstance) {
      // This callback runs after each slide change
      // If needed: you can track current index like:
      // currentIndex = sliderInstance.currentSlide;
    };
  }

  initializeGalleryNavigation();
});
</script>

Benefits of This Approach

  • Avoids conflicting events with the thumbnails.
  • Leverages FlexSlider API, ensuring transitions work even with swipe gestures or responsive behavior.
  • Avoids manual index tracking or relying on DOM class names.