Scroll Sticky Stack — Free GSAP ScrollTrigger Stacking Cards
Scroll Sticky Stack · Scroll · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<section class="ss-intro"><h1>Scroll ↓</h1><p>Cards stack and shrink as the next slides over.</p></section>
<section class="ss-stack" id="ssStack">
<article class="ss-card" style="--b:#6366f1"><span>01</span><h3>Capture</h3><p>Collect events from anywhere with one SDK.</p></article>
<article class="ss-card" style="--b:#0ea5e9"><span>02</span><h3>Process</h3><p>Transform streams in real time, no servers.</p></article>
<article class="ss-card" style="--b:#10b981"><span>03</span><h3>Visualize</h3><p>Live dashboards your whole team can read.</p></article>
<article class="ss-card" style="--b:#f59e0b"><span>04</span><h3>Act</h3><p>Trigger alerts and workflows automatically.</p></article>
</section>
<section class="ss-outro"><p>A pinned, shrinking card stack.</p></section>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0a0b13;color:#fff}
.ss-intro,.ss-outro{min-height:70vh;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;gap:10px;padding:24px}
.ss-intro h1{font-size:clamp(34px,7vw,68px);letter-spacing:-.02em}
.ss-intro p,.ss-outro p{color:#9aa0b8;font-size:16px}
.ss-stack{max-width:680px;margin:0 auto;padding:0 24px}
.ss-card{position:sticky;top:14vh;height:64vh;margin-bottom:8vh;border-radius:26px;padding:clamp(28px,5vw,56px);display:flex;flex-direction:column;justify-content:center;gap:12px;transform-origin:50% 0;will-change:transform;filter:brightness(1);background:radial-gradient(90% 90% at 20% 10%,color-mix(in srgb,var(--b) 60%,#0a0b13),#0d0f1a);border:1px solid color-mix(in srgb,var(--b) 45%,#0a0b13);box-shadow:0 -10px 40px rgba(0,0,0,.3)}
.ss-card span{font-size:14px;font-weight:800;letter-spacing:.2em;color:color-mix(in srgb,var(--b) 60%,#fff)}
.ss-card h3{font-size:clamp(28px,5vw,52px);letter-spacing:-.02em}
.ss-card p{color:#c6ccdd;font-size:clamp(15px,2.2vw,19px);max-width:420px;line-height:1.55}gsap.registerPlugin(ScrollTrigger);
var cards = gsap.utils.toArray('.ss-card');
// As each card is overtaken by the next, scale & dim it so it recedes into the stack.
cards.forEach(function (card, i) {
if (i === cards.length - 1) return; // last card never gets covered
gsap.to(card, {
scale: 0.86,
filter: 'brightness(0.6)',
ease: 'none',
scrollTrigger: {
trigger: cards[i + 1],
start: 'top bottom', // next card begins to enter
// The trigger card is ALSO position: sticky with the same top offset,
// so it can never actually reach literal viewport-top (0) -- it
// freezes at that shared offset once stuck. 'top top' asked for a
// position sticky structurally can't reach, so the scrub overshot
// past 1 and the brightness/scale extrapolated well past their
// intended floor. 'top 14vh' matches the real full-coverage moment:
// when the next card reaches ITS OWN stuck position.
end: 'top 14%',
scrub: true
}
});
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Scroll Sticky Stack — Cards That Recede as the Next Slides Over

The scroll sticky stack is the popular effect where full-height cards pin one after another and each shrinks and darkens as the following card slides up to cover it, building a tidy deck instead of scrolling away — the storytelling layout from modern product pages. This snippet combines CSS position: sticky with GSAP ScrollTrigger (from a CDN).
Sticky does the stacking
Each card is position: sticky with the same top offset, so as you scroll, a card sticks at that position while the next card scrolls up underneath the previous one's bottom and eventually pins over it. CSS sticky alone produces the overlap for free — no JavaScript needed for the pinning. The cards share a top value so they land in the same place and pile up.
GSAP adds the depth
Sticky alone would just stack flat cards; the recession comes from GSAP. For each card, a tween scales it to 0.86 and dims it with filter: brightness(0.6), driven by a ScrollTrigger whose trigger is the next card. So a card only shrinks while the card after it is moving up to cover it — start: 'top bottom' (next card enters) to end: 'top top' (next card fully overlaps). The result is each card visibly sinking back as it's buried, giving the stack real depth.
Triggering off the next element
The key idea is that each card's animation is keyed to the next card's scroll position, not its own. That's what synchronizes the shrink with the cover: the card behind reaches full small-and-dim exactly when the card in front has completely overlapped it. transform-origin: 50% 0 scales each card from its top edge so it shrinks toward where it's pinned, keeping the top aligned as it recedes.
Scrubbed and reversible
scrub: true and ease: 'none' tie the scale and brightness directly to scroll, so scrolling back up brings each buried card forward again — it grows and brightens as the covering card retreats. Nothing plays on a fixed timeline, so the stack is fully reversible and always matches the scroll position.
Cheap and smooth
The animation only changes transform and filter, both GPU-friendly, and the last card is skipped since nothing ever covers it. Because sticky handles layout and GSAP only animates compositor properties, the effect stays smooth with any number of cards.
Customizing it
Add cards (each with its own accent), change how small or dim they get, adjust the sticky top and card heights, or add a slight y offset for a fanned look. Pair it with stacking scroll cards, a scroll pin steps panel, or scroll 3d cards.
Step by step
How to Use
- 1Add the GSAP CDNsInclude gsap and ScrollTrigger from the CDN panel.
- 2Paste HTML, CSS, and JSFour tall cards render in a stack.
- 3Scroll downEach card pins, then shrinks as the next covers it.
- 4Scroll back upBuried cards grow and brighten again.
- 5Add a cardDrop in another .ss-card with an accent.
- 6Tune the recedeChange the scale and brightness targets.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
CSS does the stacking: each card is position: sticky with the same top offset, so a card sticks while the next scrolls up and pins over it. That overlap is free, with no JavaScript. GSAP adds only the depth — scaling and dimming each card as it is covered — so the two work together.
Each card's tween is triggered by the next card, not itself: from start: top bottom (the next card enters) to end: top top (the next card fully overlaps). So a card reaches its smallest, dimmest state exactly when the card in front has completely covered it, which is what makes the recession read correctly.
transform-origin: 50% 0 scales each card from its top, which is where it pins. As the card shrinks it stays aligned to its sticky position instead of drifting toward its center, so it cleanly recedes behind the covering card with the top edges matching.
Yes. scrub: true and ease: none tie the scale and brightness directly to scroll, so scrolling up brings each buried card forward again — it grows and brightens as the covering card retreats. Nothing plays on a fixed timeline, so the whole stack tracks scroll position in both directions.
Render the cards with position: sticky in CSS, then in a mount effect register ScrollTrigger and loop the card refs, creating each tween triggered by the next card. Return a cleanup that reverts the GSAP context. If cards are dynamic, refresh ScrollTrigger after they render. The sticky CSS ports unchanged.