Gravity Scroll Cards for React

Cards that stack and unstack with scroll. Gravity-style depth effect.

Component

Card 1
Card 2
Card 3

Installation

 

Usage

Import

Add the GravityScrollCards import.

import { GravityScrollCards } from "@/components/gravity-scroll-cards";

Use

Wrap your card elements.

<GravityScrollCards>{/* cards */}</GravityScrollCards>;

Guidelines

  • Pass children as cards. They stack with scroll-linked transform.
  • stackOffset and stackRotation control the stacking effect.
  • start and end control scroll range.

Props

All props are optional unless marked required. Use these to customize every aspect of the component.

PropTypeDefaultDescription
classNamestring-Additional CSS classes.
cardClassNamestring-Classes for each card wrapper.
cardWidthstring | number256Card width in pixels or CSS value (e.g. '16rem').
stackOffsetnumber24Vertical offset between stacked cards (px).
stackRotationnumber3Rotation per card in degrees.
startstring"top 80%"ScrollTrigger start.
endstring"top 20%"ScrollTrigger end.

Accessibility

  • Decorative scroll-linked visual effect that wraps arbitrary children; the wrapper itself adds no interactive controls, focusable elements, ARIA roles, or labels.
  • Any interactivity, keyboard operability, and visible focus styling must come from the card content you pass in, since the wrapper only applies positioning and transforms.
  • The wrapper exposes no roles or labels, so meaningful structure and accessible names must be supplied by the children.
  • It does not currently check prefers-reduced-motion, so the cards animate for everyone; it should be guarded with a matchMedia('(prefers-reduced-motion: reduce)') check to skip or disable the scroll animation for users who prefer reduced motion.

Performance

  • Animates only rotateZ and y (translateY) transforms plus zIndex, so the motion runs on the compositor and avoids layout and paint thrashing; zIndex changes are cheap and do not trigger reflow on their own.
  • Uses GSAP ScrollTrigger with scrub set to 1, tying progress to scroll position via GSAP's internal requestAnimationFrame ticker rather than per-frame React renders.
  • Effect cleanup calls ctx.revert() on a gsap.context, which tears down the created tweens and ScrollTrigger instances and reverts inline styles, preventing leaked listeners or stale triggers.
  • No will-change is set explicitly, so the browser is not pre-hinted to promote the cards to their own layers; transforms still composite efficiently but a will-change-transform hint could reduce first-frame jank.
  • Animation is not gated on reduced-motion or element visibility beyond the scroll range, so work continues for all users even when motion is unwanted.

Examples

Basic

Basic stacked cards.

Card 1
Card 2
Card 3
import { GravityScrollCards } from "@/components/gravity-scroll-cards";

<GravityScrollCards className="min-h-80">
  <div className="rounded-xl border bg-linear-to-br from-violet-600 to-purple-800 p-6">
    Card 1
  </div>
  <div className="rounded-xl border bg-linear-to-br from-fuchsia-600 to-pink-800 p-6">
    Card 2
  </div>
  <div className="rounded-xl border bg-linear-to-br from-cyan-600 to-blue-800 p-6">
    Card 3
  </div>
</GravityScrollCards>;

Custom stack

Custom stack offset and rotation.

Card 1
Card 2
Card 3
import { GravityScrollCards } from "@/components/gravity-scroll-cards";

<GravityScrollCards stackOffset={28} stackRotation={4}>
  {/* cards with more offset and rotation */}
</GravityScrollCards>;

Last updated on Jun 19

Made with ❤️ by Pulkit &

© 2026 Pulkit. All rights reserved

Last updated: