# The Canonical GSAP Sticky-Stack Skeleton in taste-skill: Implementation Guide

> Explore the canonical GSAP sticky-stack skeleton in taste-skill. Learn this ScrollTrigger pattern for seamless card stacking and smooth exit animations, avoiding common bugs.

- Repository: [Leon Lin/taste-skill](https://github.com/Leonxlnx/taste-skill)
- Tags: implementation-guide
- Published: 2026-05-31

---

**The canonical GSAP sticky-stack skeleton is a prescribed ScrollTrigger pattern that pins cards to the viewport top while driving their exit animations through the next card's scroll position, ensuring seamless stacking without half-triggered bugs.**

The **taste-skill** repository defines strict implementation standards for scroll-driven card stacks using GSAP. This **canonical GSAP sticky-stack skeleton** guarantees consistent pinning behavior and eliminates common animation race conditions by centralizing all transforms within a single `ScrollTrigger` context.

## What the Sticky-Stack Pattern Achieves

Each card in a vertical list pins to the top of the viewport while the subsequent card scrolls up behind it. When the next card reaches the viewport top, the previous card shrinks and fades, creating a smooth stack-like transition. The pattern ensures that **pinning triggers exactly at the viewport edge** rather than mid-scroll, preventing the "fires halfway" bug common in ad-hoc implementations.

## Critical Implementation Points

The skeleton mandates six non-negotiable technical requirements to ensure reliability and accessibility.

### Exact Viewport Pinning with `start: "top top"`

The trigger must fire precisely when the element reaches the viewport top. This prevents the card from hanging partially off-screen or activating too early.

```tsx
ScrollTrigger.create({
  trigger: card,
  start: "top top", // Critical: pins exactly at viewport top
  // ...
});

```

### Seamless Spacing with `pin: true` and `pinSpacing: false`

These properties keep the element fixed during scroll without injecting extra DOM space that would break the stack's visual continuity.

```tsx
pin: true,
pinSpacing: false, // Prevents extra space between stacked cards

```

### Static Final Card

**All cards except the last must be pinned.** The final card remains static to allow the stack to release naturally; otherwise, the pinned container would never unpin, leaving users stranded at the bottom of the scroll container.

### Animation Driven by the Next Card

The current card's exit transform (scale and opacity) must be controlled by the **next** card's `ScrollTrigger` using `scrub: true`. This creates the visual effect of the current card receding as the upcoming card arrives.

```tsx
gsap.to(card, {
  scale: 0.92,
  opacity: 0.55,
  ease: "none",
  scrollTrigger: {
    trigger: cardEls[i + 1], // Driven by next card
    start: "top bottom",
    end: "top top",
    scrub: true, // Smooth animation tied to scroll position
  },
});

```

### Reduced Motion Accessibility

The implementation must respect user preferences through a `useReducedMotion` guard. When detected, the entire effect disables immediately to satisfy accessibility requirements.

```tsx
const reduce = useReducedMotion();

useEffect(() => {
  if (reduce || !ref.current) return;
  // ... GSAP setup
}, [reduce]);

```

### Scoped Context Cleanup

All GSAP work must live inside a `gsap.context` tied to the container ref. This guarantees proper cleanup via `ctx.revert()` on unmount, preventing memory leaks and orphaned ScrollTrigger instances.

```tsx
const ctx = gsap.context(() => {
  // All ScrollTrigger and animation logic here
}, ref);

return () => ctx.revert(); // Clean up on unmount

```

## Complete React/Next.js Implementation

Below is the canonical component ready for production use. Per the specification in [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md) at lines 665-674, this implements all critical points including the reduce-motion guard and scoped context cleanup.

```tsx
"use client";
import { useRef, useEffect } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { useReducedMotion } from "motion/react";

gsap.registerPlugin(ScrollTrigger);

export function StickyStack({ cards }: { cards: React.ReactNode[] }) {
  const ref = useRef<HTMLDivElement>(null);
  const reduce = useReducedMotion();

  useEffect(() => {
    if (reduce || !ref.current) return;

    // Scoped context for automatic cleanup
    const ctx = gsap.context(() => {
      const cardEls = gsap.utils.toArray<HTMLElement>(".stack-card");

      cardEls.forEach((card, i) => {
        if (i === cardEls.length - 1) return; // Last card stays static

        // Pin current card exactly at viewport top
        ScrollTrigger.create({
          trigger: card,
          start: "top top",
          endTrigger: cardEls[cardEls.length - 1],
          end: "top top",
          pin: true,
          pinSpacing: false,
        });

        // Animate current card based on next card's scroll position
        gsap.to(card, {
          scale: 0.92,
          opacity: 0.55,
          ease: "none",
          scrollTrigger: {
            trigger: cardEls[i + 1],
            start: "top bottom",
            end: "top top",
            scrub: true,
          },
        });
      });
    }, ref);

    return () => ctx.revert();
  }, [reduce]);

  return (
    <div ref={ref} className="relative">
      {cards.map((card, i) => (
        <div
          key={i}
          className="stack-card sticky top-0 min-h-[100dvh] flex items-center justify-center"
        >
          {card}
        </div>
      ))}
    </div>
  );
}

```

## Source File References

The official specification resides in the taste-skill repository across two key files:

- **[`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md)** (lines 665-674): Contains the full skeleton with inline comments defining the canonical pattern in Section 5.A.
- **[`CHANGELOG.md`](https://github.com/Leonxlnx/taste-skill/blob/main/CHANGELOG.md)** (line 69): Documents the §5.A GSAP Sticky-Stack entry, summarizing the pattern for release tracking and version control.

## Summary

- **`start: "top top"`** ensures pinning triggers exactly when cards hit the viewport edge, eliminating half-fired animations.
- **`pinSpacing: false`** creates seamless stacking without extra DOM space between cards.
- **Static final card** allows the stack to release naturally at the end of the scroll container.
- **Next-card-driven transforms** with `scrub: true` create the shrink-and-fade effect as subsequent cards arrive.
- **Reduced-motion guards** and **`gsap.context`** ensure accessibility compliance and prevent memory leaks.

## Frequently Asked Questions

### What makes this pattern "canonical" in taste-skill?

The pattern is designated canonical because it is the **prescribed standard** documented in Section 5.A of [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md). It centralizes all scroll-driven transforms in a single `ScrollTrigger` context, eliminating inconsistent implementations and guaranteeing that pinning behavior remains uniform across all agents and developers using the repository.

### Why must the last card remain unpinned?

The final card acts as the **scroll release mechanism**. If all cards were pinned, the ScrollTrigger container would never unpin, preventing users from scrolling past the stack to subsequent content. By leaving the last card static, the stack naturally concludes and releases the viewport lock.

### How does the reduce-motion guard integrate with the skeleton?

The `useReducedMotion` hook from `motion/react` returns a boolean that immediately exits the GSAP setup effect when true. This **prevents any ScrollTrigger registration or pinning**, ensuring users who prefer reduced motion encounter a standard static scroll rather than animated transformations, satisfying WCAG accessibility guidelines.

### Where is the official sticky-stack specification maintained?

The authoritative definition lives in [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md) at lines 665-674, with additional context in [`CHANGELOG.md`](https://github.com/Leonxlnx/taste-skill/blob/main/CHANGELOG.md) at line 69 under the "§5.A GSAP Sticky-Stack" entry. These files serve as the single source of truth for implementation details and version updates.