Tailwind UI Pattern Registry for humans and agents

scroll-reveal fade entrance gsap gsap

Fade Up

Fade Up

Elements fade and rise into view as they enter the viewport using GSAP ScrollTrigger.

GSAP Scroll
Live Preview

Dependencies

GSAP v3.12.5 ScrollTrigger v3.12.5
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Fade Up — Motion Recipe</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
  <style>
    @media (prefers-reduced-motion: reduce) {
      .fade-up { opacity: 1 !important; transform: none !important; }
    }
  </style>
</head>
<body class="bg-slate-900 text-slate-200 p-8 font-sans">

  <!-- Hero -->
  <div class="mx-auto mb-32 max-w-3xl pt-16 text-center fade-up">
    <span class="mb-6 inline-block rounded-full border border-indigo-500/20 bg-indigo-500/10 px-3.5 py-1 text-[0.7rem] font-semibold uppercase tracking-widest text-indigo-400">Scroll Reveal</span>
    <h1 class="mb-4 text-5xl font-black leading-tight text-slate-100">Elements fade up<br>as you scroll</h1>
    <p class="mx-auto mb-8 max-w-sm text-lg leading-relaxed text-slate-500">Scroll down to see each section animate into view using GSAP ScrollTrigger.</p>
    <a href="#" class="inline-block cursor-default rounded-xl bg-indigo-600 px-7 py-2.5 text-sm font-semibold text-white">Get started</a>
  </div>

  <!-- Section 1 -->
  <div class="mx-auto mb-24 max-w-3xl fade-up">
    <p class="mb-3 text-xs font-semibold uppercase tracking-[0.12em] text-slate-500">Why it works</p>
    <h2 class="mb-4 text-3xl font-black leading-tight text-slate-100">Subtle motion<br>builds focus</h2>
    <p class="max-w-[560px] text-base leading-relaxed text-slate-400">Fade-up reveals guide the eye naturally through the page. Each element earns attention in sequence, reducing cognitive load and increasing readability.</p>
  </div>

  <div class="mx-auto mb-24 max-w-3xl h-px bg-white/[0.06]"></div>

  <!-- Cards -->
  <div class="mx-auto mb-24 max-w-3xl grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
    <div class="fade-up rounded-2xl border border-white/[0.08] bg-white/[0.04] p-6">
      <div class="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-indigo-500/15 text-indigo-400">
        <svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z"/></svg>
      </div>
      <p class="mb-1 text-sm font-semibold text-slate-100">Instant feedback</p>
      <p class="text-xs leading-relaxed text-slate-500">Animations respond to scroll position in real time, with no jank.</p>
    </div>
    <div class="fade-up rounded-2xl border border-white/[0.08] bg-white/[0.04] p-6">
      <div class="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-indigo-500/15 text-indigo-400">
        <svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
      </div>
      <p class="mb-1 text-sm font-semibold text-slate-100">Accessible</p>
      <p class="text-xs leading-relaxed text-slate-500">Respects prefers-reduced-motion — elements show immediately for users who prefer it.</p>
    </div>
    <div class="fade-up rounded-2xl border border-white/[0.08] bg-white/[0.04] p-6">
      <div class="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-indigo-500/15 text-indigo-400">
        <svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z"/></svg>
      </div>
      <p class="mb-1 text-sm font-semibold text-slate-100">5 lines of JS</p>
      <p class="text-xs leading-relaxed text-slate-500">GSAP handles all the heavy lifting. You just describe the from-state.</p>
    </div>
    <div class="fade-up rounded-2xl border border-white/[0.08] bg-white/[0.04] p-6">
      <div class="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-indigo-500/15 text-indigo-400">
        <svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3v11.25A2.25 2.25 0 006 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0118 16.5h-2.25m-7.5 0h7.5m-7.5 0l-1 3m8.5-3l1 3m0 0l.5 1.5m-.5-1.5h-9.5m0 0l-.5 1.5"/></svg>
      </div>
      <p class="mb-1 text-sm font-semibold text-slate-100">Framework-free</p>
      <p class="text-xs leading-relaxed text-slate-500">Works with vanilla JS, React, Vue, Astro — any stack that renders HTML.</p>
    </div>
  </div>

  <div class="mx-auto mb-24 max-w-3xl h-px bg-white/[0.06]"></div>

  <!-- Section 2 -->
  <div class="mx-auto mb-24 max-w-3xl fade-up">
    <p class="mb-3 text-xs font-semibold uppercase tracking-[0.12em] text-slate-500">Customization</p>
    <h2 class="mb-4 text-3xl font-black leading-tight text-slate-100">Tune every<br>parameter</h2>
    <p class="max-w-[560px] text-base leading-relaxed text-slate-400">Control duration, distance, easing, and the scroll trigger point. GSAP's ScrollTrigger offers precise control over when animations start and end relative to the viewport.</p>
  </div>

  <div class="mx-auto mb-24 max-w-3xl grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
    <div class="fade-up rounded-2xl border border-white/[0.08] bg-white/[0.04] p-6">
      <p class="mb-1 text-sm font-semibold text-slate-100">duration</p>
      <p class="text-xs leading-relaxed text-slate-500">Default 0.7s. Shorter for micro-elements, longer for hero sections.</p>
    </div>
    <div class="fade-up rounded-2xl border border-white/[0.08] bg-white/[0.04] p-6">
      <p class="mb-1 text-sm font-semibold text-slate-100">y offset</p>
      <p class="text-xs leading-relaxed text-slate-500">Default 40px. Larger values create more dramatic entrances.</p>
    </div>
    <div class="fade-up rounded-2xl border border-white/[0.08] bg-white/[0.04] p-6">
      <p class="mb-1 text-sm font-semibold text-slate-100">ease</p>
      <p class="text-xs leading-relaxed text-slate-500">power2.out gives a natural deceleration. Try expo.out for luxury feel.</p>
    </div>
    <div class="fade-up rounded-2xl border border-white/[0.08] bg-white/[0.04] p-6">
      <p class="mb-1 text-sm font-semibold text-slate-100">start trigger</p>
      <p class="text-xs leading-relaxed text-slate-500">"top 85%" means the top of the element hits 85% down the viewport.</p>
    </div>
  </div>

  <div class="h-16"></div>

  <script>
    gsap.registerPlugin(ScrollTrigger);

    gsap.utils.toArray('.fade-up').forEach((el) => {
      gsap.from(el, {
        scrollTrigger: {
          trigger: el,
          start: 'top 88%',
          toggleActions: 'play none none none',
        },
        opacity: 0,
        y: 40,
        duration: 0.7,
        ease: 'power2.out',
      });
    });
  </script>
</body>
</html>

Each element with .fade-up gets its own independent ScrollTrigger that fires once when the element’s top edge hits 88% down the viewport. No JavaScript class toggling — GSAP animates directly from the defined from state to the element’s natural CSS position.

The toggleActions: 'play none none none' setting means the animation only plays once on enter and never reverses on scroll-up, which is the expected behavior for page load reveals.

Pair with a stagger for grids: wrap elements in a container and use gsap.from('.child', { stagger: 0.08, scrollTrigger: { trigger: '.parent' } }) instead of individual triggers.