🚀

Update available

We just released a new resource or update, refresh the Vault to access the latest version.

Cancel

Refresh now

Harri

Profile Picture

Harri

Lemke

Overlapping Slider

Documentation

Webflow

Code

Setup: External Scripts

HTML

Copy
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/Draggable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/InertiaPlugin.min.js"></script>

Step 1: Add HTML

HTML

Copy
<div data-overlap-slider-init="" class="overlapping-slider__wrap">
  <div data-overlap-slider-collection="" class="overlapping-slider__collection">
    <div data-overlap-slider-list="" class="overlapping-slider__list">
      <div data-overlap-slider-item="" class="overlapping-slider__item">
        <div class="demo-card">
          <h2 class="demo-card__h">“These meal prep boxes honestly changed my evenings. Everything comes chopped, seasoned and labeled, the recipes are easy to follow, and dinner is on the table in like 15 minutes. Love it. 😍”</h2>
          <div class="demo-card__bottom">
            <div class="demo__card-avatar"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 28 28" fill="none" class="demo-card__avatar-icon">
                <path d="M13.9987 13.4168C16.576 13.4168 18.6654 11.3275 18.6654 8.75016C18.6654 6.17283 16.576 4.0835 13.9987 4.0835C11.4214 4.0835 9.33203 6.17283 9.33203 8.75016C9.33203 11.3275 11.4214 13.4168 13.9987 13.4168Z" stroke="currentColor" stroke-width="1.5"></path>
                <path d="M5.25 24.5002V20.6619C5.25 20.1315 5.46071 19.6228 5.83579 19.2477L7.87597 17.2075C8.41163 16.6719 9.20397 16.4848 9.92264 16.7244L13.3675 17.8727C13.7781 18.0095 14.2219 18.0095 14.6325 17.8727L18.0774 16.7244C18.796 16.4848 19.5884 16.6719 20.124 17.2075L22.1642 19.2477C22.5393 19.6228 22.75 20.1315 22.75 20.6619V24.5002" stroke="currentColor" stroke-width="1.5"></path>
              </svg>
            </div>
            <span class="demo-card__p">Nina Calder</span>
          </div>
        </div>
      </div>
      <div data-overlap-slider-item="" class="overlapping-slider__item">
        <div class="demo-card">
          <h2 class="demo-card__h">“I’m terrible at planning meals, but this box makes it foolproof. Fresh veggies, good proteins, and zero guesswork. I eat healthier now and barely ever order takeaway anymore.”</h2>
          <div class="demo-card__bottom">
            <div class="demo__card-avatar"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 28 28" fill="none" class="demo-card__avatar-icon">
                <path d="M13.9987 13.4168C16.576 13.4168 18.6654 11.3275 18.6654 8.75016C18.6654 6.17283 16.576 4.0835 13.9987 4.0835C11.4214 4.0835 9.33203 6.17283 9.33203 8.75016C9.33203 11.3275 11.4214 13.4168 13.9987 13.4168Z" stroke="currentColor" stroke-width="1.5"></path>
                <path d="M5.25 24.5002V20.6619C5.25 20.1315 5.46071 19.6228 5.83579 19.2477L7.87597 17.2075C8.41163 16.6719 9.20397 16.4848 9.92264 16.7244L13.3675 17.8727C13.7781 18.0095 14.2219 18.0095 14.6325 17.8727L18.0774 16.7244C18.796 16.4848 19.5884 16.6719 20.124 17.2075L22.1642 19.2477C22.5393 19.6228 22.75 20.1315 22.75 20.6619V24.5002" stroke="currentColor" stroke-width="1.5"></path>
              </svg>
            </div>
            <span class="demo-card__p">Marcus Roque</span>
          </div>
        </div>
      </div>
      <div data-overlap-slider-item="" class="overlapping-slider__item">
        <div class="demo-card">
          <h2 class="demo-card__h">“Portions are perfect and the flavors are way better than I expected from a subscription. I just open a bag, toss it in the pan and feel like a real cook without any stress.”</h2>
          <div class="demo-card__bottom">
            <div class="demo__card-avatar"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 28 28" fill="none" class="demo-card__avatar-icon">
                <path d="M13.9987 13.4168C16.576 13.4168 18.6654 11.3275 18.6654 8.75016C18.6654 6.17283 16.576 4.0835 13.9987 4.0835C11.4214 4.0835 9.33203 6.17283 9.33203 8.75016C9.33203 11.3275 11.4214 13.4168 13.9987 13.4168Z" stroke="currentColor" stroke-width="1.5"></path>
                <path d="M5.25 24.5002V20.6619C5.25 20.1315 5.46071 19.6228 5.83579 19.2477L7.87597 17.2075C8.41163 16.6719 9.20397 16.4848 9.92264 16.7244L13.3675 17.8727C13.7781 18.0095 14.2219 18.0095 14.6325 17.8727L18.0774 16.7244C18.796 16.4848 19.5884 16.6719 20.124 17.2075L22.1642 19.2477C22.5393 19.6228 22.75 20.1315 22.75 20.6619V24.5002" stroke="currentColor" stroke-width="1.5"></path>
              </svg>
            </div>
              <span class="demo-card__p">Elena Strauss</span>
          </div>
        </div>
      </div>
      <div data-overlap-slider-item="" class="overlapping-slider__item">
        <div class="demo-card">
          <h2 class="demo-card__h">“These boxes save me so much time after work. No grocery queues, no chopping mountains of vegetables, just quick instructions and tasty food that actually fits my diet.”</h2>
          <div class="demo-card__bottom">
            <div class="demo__card-avatar"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 28 28" fill="none" class="demo-card__avatar-icon">
                <path d="M13.9987 13.4168C16.576 13.4168 18.6654 11.3275 18.6654 8.75016C18.6654 6.17283 16.576 4.0835 13.9987 4.0835C11.4214 4.0835 9.33203 6.17283 9.33203 8.75016C9.33203 11.3275 11.4214 13.4168 13.9987 13.4168Z" stroke="currentColor" stroke-width="1.5"></path>
                <path d="M5.25 24.5002V20.6619C5.25 20.1315 5.46071 19.6228 5.83579 19.2477L7.87597 17.2075C8.41163 16.6719 9.20397 16.4848 9.92264 16.7244L13.3675 17.8727C13.7781 18.0095 14.2219 18.0095 14.6325 17.8727L18.0774 16.7244C18.796 16.4848 19.5884 16.6719 20.124 17.2075L22.1642 19.2477C22.5393 19.6228 22.75 20.1315 22.75 20.6619V24.5002" stroke="currentColor" stroke-width="1.5"></path>
              </svg>
            </div>
              <span class="demo-card__p">Jared Kimani</span>
          </div>
        </div>
      </div>
      <div data-overlap-slider-item="" class="overlapping-slider__item">
        <div class="demo-card">
          <h2 class="demo-card__h">“I love how the meals are balanced without feeling like ‘diet food’. Sauces are delicious, ingredients stay super fresh, and cleanup is basically one pan and a plate. 🙌”</h2>
          <div class="demo-card__bottom">
            <div class="demo__card-avatar"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 28 28" fill="none" class="demo-card__avatar-icon">
                <path d="M13.9987 13.4168C16.576 13.4168 18.6654 11.3275 18.6654 8.75016C18.6654 6.17283 16.576 4.0835 13.9987 4.0835C11.4214 4.0835 9.33203 6.17283 9.33203 8.75016C9.33203 11.3275 11.4214 13.4168 13.9987 13.4168Z" stroke="currentColor" stroke-width="1.5"></path>
                <path d="M5.25 24.5002V20.6619C5.25 20.1315 5.46071 19.6228 5.83579 19.2477L7.87597 17.2075C8.41163 16.6719 9.20397 16.4848 9.92264 16.7244L13.3675 17.8727C13.7781 18.0095 14.2219 18.0095 14.6325 17.8727L18.0774 16.7244C18.796 16.4848 19.5884 16.6719 20.124 17.2075L22.1642 19.2477C22.5393 19.6228 22.75 20.1315 22.75 20.6619V24.5002" stroke="currentColor" stroke-width="1.5"></path>
              </svg>
            </div>
              <span class="demo-card__p">Priya Desai</span>
          </div>
        </div>
      </div>
      <div data-overlap-slider-item="" class="overlapping-slider__item">
        <div class="demo-card">
          <h2 class="demo-card__h">“The variety surprises me every week. I’ve tried dishes I’d never think to make myself, but everything is simple enough that I can cook while half-asleep and still nail it.”</h2>
          <div class="demo-card__bottom">
            <div class="demo__card-avatar"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 28 28" fill="none" class="demo-card__avatar-icon">
                <path d="M13.9987 13.4168C16.576 13.4168 18.6654 11.3275 18.6654 8.75016C18.6654 6.17283 16.576 4.0835 13.9987 4.0835C11.4214 4.0835 9.33203 6.17283 9.33203 8.75016C9.33203 11.3275 11.4214 13.4168 13.9987 13.4168Z" stroke="currentColor" stroke-width="1.5"></path>
                <path d="M5.25 24.5002V20.6619C5.25 20.1315 5.46071 19.6228 5.83579 19.2477L7.87597 17.2075C8.41163 16.6719 9.20397 16.4848 9.92264 16.7244L13.3675 17.8727C13.7781 18.0095 14.2219 18.0095 14.6325 17.8727L18.0774 16.7244C18.796 16.4848 19.5884 16.6719 20.124 17.2075L22.1642 19.2477C22.5393 19.6228 22.75 20.1315 22.75 20.6619V24.5002" stroke="currentColor" stroke-width="1.5"></path>
              </svg>
            </div>
              <span class="demo-card__p">Tomás Herrera</span>
          </div>
        </div>
      </div>
      <div data-overlap-slider-item="" class="overlapping-slider__item">
        <div class="demo-card">
          <h2 class="demo-card__h">“These meal prep kits made lunches at work a lot less boring. I cook two portions at night, pack one for tomorrow, and I’m done. No more sad sandwiches or vending machines.” </h2>
          <div class="demo-card__bottom">
            <div class="demo__card-avatar"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 28 28" fill="none" class="demo-card__avatar-icon">
                <path d="M13.9987 13.4168C16.576 13.4168 18.6654 11.3275 18.6654 8.75016C18.6654 6.17283 16.576 4.0835 13.9987 4.0835C11.4214 4.0835 9.33203 6.17283 9.33203 8.75016C9.33203 11.3275 11.4214 13.4168 13.9987 13.4168Z" stroke="currentColor" stroke-width="1.5"></path>
                <path d="M5.25 24.5002V20.6619C5.25 20.1315 5.46071 19.6228 5.83579 19.2477L7.87597 17.2075C8.41163 16.6719 9.20397 16.4848 9.92264 16.7244L13.3675 17.8727C13.7781 18.0095 14.2219 18.0095 14.6325 17.8727L18.0774 16.7244C18.796 16.4848 19.5884 16.6719 20.124 17.2075L22.1642 19.2477C22.5393 19.6228 22.75 20.1315 22.75 20.6619V24.5002" stroke="currentColor" stroke-width="1.5"></path>
              </svg>
            </div>
              <span class="demo-card__p">Leah Morrison</span>
          </div>
        </div>
      </div>
      <div data-overlap-slider-item="" class="overlapping-slider__item">
        <div class="demo-card">
          <h2 class="demo-card__h">“I started this to stop wasting groceries and it worked. Everything is pre-measured, there’s almost no trash, and my fridge finally looks organized instead of chaotic.”</h2>
          <div class="demo-card__bottom">
            <div class="demo__card-avatar"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 28 28" fill="none" class="demo-card__avatar-icon">
                <path d="M13.9987 13.4168C16.576 13.4168 18.6654 11.3275 18.6654 8.75016C18.6654 6.17283 16.576 4.0835 13.9987 4.0835C11.4214 4.0835 9.33203 6.17283 9.33203 8.75016C9.33203 11.3275 11.4214 13.4168 13.9987 13.4168Z" stroke="currentColor" stroke-width="1.5"></path>
                <path d="M5.25 24.5002V20.6619C5.25 20.1315 5.46071 19.6228 5.83579 19.2477L7.87597 17.2075C8.41163 16.6719 9.20397 16.4848 9.92264 16.7244L13.3675 17.8727C13.7781 18.0095 14.2219 18.0095 14.6325 17.8727L18.0774 16.7244C18.796 16.4848 19.5884 16.6719 20.124 17.2075L22.1642 19.2477C22.5393 19.6228 22.75 20.1315 22.75 20.6619V24.5002" stroke="currentColor" stroke-width="1.5"></path>
              </svg>
            </div>
              <span class="demo-card__p">Caleb Wright</span>
          </div>
        </div>
      </div>
      <div data-overlap-slider-item="" class="overlapping-slider__item">
        <div class="demo-card">
          <h2 class="demo-card__h">“The step-by-step cards are super clear, even for someone who usually burns toast. I feel confident in the kitchen now, and my friends keep asking for the recipes. 😅”</h2>
          <div class="demo-card__bottom">
            <div class="demo__card-avatar"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 28 28" fill="none" class="demo-card__avatar-icon">
                <path d="M13.9987 13.4168C16.576 13.4168 18.6654 11.3275 18.6654 8.75016C18.6654 6.17283 16.576 4.0835 13.9987 4.0835C11.4214 4.0835 9.33203 6.17283 9.33203 8.75016C9.33203 11.3275 11.4214 13.4168 13.9987 13.4168Z" stroke="currentColor" stroke-width="1.5"></path>
                <path d="M5.25 24.5002V20.6619C5.25 20.1315 5.46071 19.6228 5.83579 19.2477L7.87597 17.2075C8.41163 16.6719 9.20397 16.4848 9.92264 16.7244L13.3675 17.8727C13.7781 18.0095 14.2219 18.0095 14.6325 17.8727L18.0774 16.7244C18.796 16.4848 19.5884 16.6719 20.124 17.2075L22.1642 19.2477C22.5393 19.6228 22.75 20.1315 22.75 20.6619V24.5002" stroke="currentColor" stroke-width="1.5"></path>
              </svg>
            </div>
              <span class="demo-card__p">Sophie Lang</span>
          </div>
        </div>
      </div>
      <div data-overlap-slider-item="" class="overlapping-slider__item">
        <div class="demo-card">
          <h2 class="demo-card__h">“Honestly the best habit I’ve picked up all year. These boxes keep me cooking at home, eating real food, and still give me my evenings back. Feels like cheating at adulting.”</h2>
          <div class="demo-card__bottom">
            <div class="demo__card-avatar"><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 28 28" fill="none" class="demo-card__avatar-icon">
                <path d="M13.9987 13.4168C16.576 13.4168 18.6654 11.3275 18.6654 8.75016C18.6654 6.17283 16.576 4.0835 13.9987 4.0835C11.4214 4.0835 9.33203 6.17283 9.33203 8.75016C9.33203 11.3275 11.4214 13.4168 13.9987 13.4168Z" stroke="currentColor" stroke-width="1.5"></path>
                <path d="M5.25 24.5002V20.6619C5.25 20.1315 5.46071 19.6228 5.83579 19.2477L7.87597 17.2075C8.41163 16.6719 9.20397 16.4848 9.92264 16.7244L13.3675 17.8727C13.7781 18.0095 14.2219 18.0095 14.6325 17.8727L18.0774 16.7244C18.796 16.4848 19.5884 16.6719 20.124 17.2075L22.1642 19.2477C22.5393 19.6228 22.75 20.1315 22.75 20.6619V24.5002" stroke="currentColor" stroke-width="1.5"></path>
              </svg>
            </div>
              <span class="demo-card__p">Dylan Kozlov</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Step 2: Add CSS

CSS

Copy
.overlapping-slider__wrap {
  width: 100%;
}

.overlapping-slider__collection {
  justify-content: flex-start;
  align-items: flex-start;
  width: 100%;
  display: flex;
  position: relative;
}

.overlapping-slider__list {
  flex-flow: row;
  flex: none;
  justify-content: flex-start;
  align-items: center;
  display: flex;
  position: relative;
}

.overlapping-slider__item {
  flex: none;
  margin-right: 1.5em;
}

.demo-card {
  grid-column-gap: 2em;
  grid-row-gap: 2em;
  aspect-ratio: 3 / 4;
  background-color: #fff;
  border: .1875em solid #000;
  border-radius: 1em;
  flex-flow: column;
  justify-content: space-between;
  align-items: flex-start;
  width: 24em;
  max-width: 85vw;
  padding: 3em 2em 2em;
  display: flex;
}

.demo-card__h {
  margin-top: 0;
  margin-bottom: 0;
  font-size: 1.5em;
  font-weight: 600;
  line-height: 1.3;
}

.demo-card__bottom {
  grid-column-gap: .75em;
  grid-row-gap: .75em;
  flex-flow: row;
  justify-content: flex-start;
  align-items: center;
  display: flex;
}

.demo__card-avatar {
  color: #fff;
  background-color: #ffce16;
  border-radius: 100em;
  flex: none;
  width: 3em;
  height: 3em;
  padding: .625em;
}

.demo-card__avatar-icon {
  width: 100%;
  height: 100%;
}

.demo-card__p {
  font-size: 1.25em;
  font-weight: 600;
  line-height: 1;
}

Step 2: Add Javascript

Step 3: Add Javascript

Javascript

Copy
function initOverlappingSlider() {
  const inits = document.querySelectorAll('[data-overlap-slider-init]');
  if (!inits.length) return;

  inits.forEach(setupOverlappingSlider);

  function setupOverlappingSlider(init) {
    // --- attributes with defaults
    const minScale = +(init.getAttribute('data-scale')  ?? 0.45);
    const maxRotation = +(init.getAttribute('data-rotate') ?? -8);
    const inertia = true;

    const wrap = init.querySelector('[data-overlap-slider-collection]');
    const slider = init.querySelector('[data-overlap-slider-list]');
    const slides = Array.from(init.querySelectorAll('[data-overlap-slider-item]'));

    if (!wrap || !slider || !slides.length) {
      console.warn("OverlappingSlider: missing required structure. Check Osmo Vault documentation please.");
      return;
    }
    
    wrap.style.touchAction = 'none';
    wrap.style.userSelect = 'none';

    let spacing = 0;
    let slideW = 0;
    let maxDrag = 0;
    let dragX = 0;
    let draggable;

    // simple clamp that always uses latest maxDrag
    function clamp(value) {
      if (maxDrag <= 0) return 0;
      return Math.min(Math.max(value, 0), maxDrag);
    }

    function update() {
      // move the whole list
      gsap.set(slider, { x: -dragX });

      // update each slide's overlap transform
      slides.forEach((slide, i) => {
        const threshold = i * spacing;
        const local = Math.max(0, dragX - threshold);
        const t = spacing > 0 ? Math.min(local / spacing, 1) : 0;

        gsap.set(slide, {
          x: local,
          scale: 1 - (1 - minScale) * t,
          rotation: maxRotation * t,
          transformOrigin: '75% center'
        });
      });
    }

    function recalc() {
      if (!slides.length) return;

      // measure one slide to get width + margin-right as "gap"
      const style = getComputedStyle(slides[0]);
      const gapRight = parseFloat(style.marginRight) || 0;

      slideW = slides[0].offsetWidth;
      spacing = slideW + gapRight;
      maxDrag = spacing * (slides.length - 1);

      // keep dragX within new bounds
      dragX = clamp(dragX);
      update();

      if (draggable) {
        draggable.applyBounds({ minX: -maxDrag, maxX: 0 });
      }
    }

    // create draggable
    draggable = Draggable.create(slider, {
      type: 'x',
      bounds: { minX: -maxDrag, maxX: 0 }, // will be updated after recalc
      inertia,
      maxDuration: 1,
      snap: true
        ? (raw) => {
            // raw is the x value
            const d = clamp(-raw);
            const idx = spacing > 0 ? Math.round(d / spacing) : 0;
            return -idx * spacing;
          }
        : false,
      onDrag() {
        dragX = clamp(-this.x);
        update();
      },
      onThrowUpdate() {
        dragX = clamp(-this.x);
        update();
      }
    })[0];

    // recalc on resize
    const ro = new ResizeObserver(() => {
      recalc();
    });
    ro.observe(init);
    
    // keyboard navigation (arrow left/right)
    let active = false;
    let currentIndex = 0;

    // helper function to switch slides
    function goToSlide(idx) {
      idx = Math.max(0, Math.min(idx, slides.length - 1));
      currentIndex = idx;
    
      const targetX = idx * spacing;
    
      gsap.to({ value: dragX }, {
        value: targetX,
        duration: 0.35,
        ease: "power4.out",
        onUpdate: function () {
          dragX = this.targets()[0].value;
          gsap.set(slider, { x: -dragX });
          update(); // animate overlap transforms properly
        }
      });
    
      wrap.setAttribute("aria-label", `Slide ${idx + 1} of ${slides.length}`);
    }

    // Observe visibility
    const io = new IntersectionObserver(entries => {
      active = entries[0].isIntersecting;
    }, {
      threshold: 0.25 // slider must be at least 25% visible
    });

    io.observe(init);
    
    // Aria labels for accessibility
    wrap.setAttribute("role", "region");
    wrap.setAttribute("aria-roledescription", "carousel");
    wrap.setAttribute("aria-label", "Testimonial slider");

    // key listener
    function onKey(e) {
      if (!active) return; // only respond when slider in view

      if (e.key === "ArrowLeft") {
        e.preventDefault();
        goToSlide(currentIndex - 1);
      }

      if (e.key === "ArrowRight") {
        e.preventDefault();
        goToSlide(currentIndex + 1);
      }
    }
    window.addEventListener("keydown", onKey);

    // initial layout
    recalc();
  }
}

// Initialize Overlapping Slider
document.addEventListener("DOMContentLoaded", function () {
  initOverlappingSlider();
});

Implementation

Init

Use [data-overlap-slider-init] to activate the Overlapping Slider instance, allowing all measurements and draggable behavior to initialize correctly.

Wrap

Use [data-overlap-slider-collection] to define the outer container that handles ARIA roles, visibility tracking, and keyboard-driven slide announcements.

Slider

Use [data-overlap-slider-list] to create the horizontally draggable list element that GSAP positions using drag values and inertia. This is the direct parent of all slides. Make sure it keeps the combined width of all slides. In our example, we do this by setting the wrap (see previous step) to display: flex; which then allows us to set the slider list to flex: none;

Slides

Use [data-overlap-slider-item] to register each card as a draggable slide that participates in the overlapping transform logic.

Space between

Simply add some margin-right to your slides in CSS to define the space or 'gap' between all slides.

Scale

Use [data-scale] (default 0.45) to set the minimum scale applied to background cards as they move behind the active slide.

Rotation

Use [data-rotate] (default -8) to define how much each background card tilts while transitioning between positions.

Slide content

We recommend to leave the slides themselves pretty much unstyled. In our demo we just place an actual 'card' div inside of each [data-overlap-slider-item]. This way, it's much easier to re-use this slider across multiple instances, with different content and/or styling.

Keyboard Navigation

The slider supports arrow-key navigation out of the box, automatically activating when the component becomes at least 25% visible to ensure accessible slide control.

Live preview

Osmo Robot AI

Copy context for AI

Beta

Webflow

HTML/CSS/JS

Save video

Copy share link

Resource details

  • Published

    November 26, 2025

  • Category

    Sliders & Marquees

  • Popularity

    1.2K visitors

  • Need help?

    Join Slack

Slider
Slideshow
Draggable
GSAP
Inertia
Grab
Javascript
Physics

Original source

Ilja van EckIlja van Eck

Creator Credits

We always strive to credit creators as accurately as possible. While similar concepts might appear online, we aim to provide proper and respectful attribution.

s