Oblivion
Oblivion
Docs
Go back
Component by
OV
Oblivion

1. Solid Foundation

Start with a robust, accessible foundation built on modern web standards.

2. Liquid Animations

Apply cinematic, physics-based animations using Framer Motion.

3. Perfect Consistency

Ensure everything looks absolutely pixel-perfect on any device.

TSX
stacking-cards
"use client"

import { motion, useScroll, useTransform } from "framer-motion"
import { useRef } from "react"
import { Package, Smartphone, Zap } from "lucide-react"

export const componentMeta = {
  name: "Stacking Cards",
  description: "Cards that dynamically stack on top of each other as you scroll down the page.",
  props: {}
}

const cards = [
  { 
    title: "1. Solid Foundation", 
    description: "Start with a robust, accessible foundation built on modern web standards.", 
    color: "bg-white text-black border-black/10 dark:bg-zinc-900 dark:text-white dark:border-white/10",
    icon: Package
  },
  { 
    title: "2. Liquid Animations", 
    description: "Apply cinematic, physics-based animations using Framer Motion.", 
    color: "bg-black text-white border-white/20 dark:bg-zinc-800 dark:border-white/10",
    icon: Zap
  },
  { 
    title: "3. Perfect Consistency", 
    description: "Ensure everything looks absolutely pixel-perfect on any device.", 
    color: "bg-neutral-800 text-white border-white/10 dark:bg-zinc-700 dark:border-white/5",
    icon: Smartphone
  }
]

export default function StackingCards() {
  const containerRef = useRef<HTMLDivElement>(null)
  const { scrollYProgress } = useScroll({
    target: containerRef,
    offset: ["start start", "end end"]
  })

  return (
    <div className="w-full bg-neutral-100 dark:bg-black p-4 rounded-[2rem] border border-black/5 dark:border-white/5 relative h-[600px] overflow-y-auto no-scrollbar">
      <div ref={containerRef} className="relative w-full h-[150vh] flex flex-col items-center">
        {cards.map((card, i) => {
          const targetScale = 1 - (cards.length - 1 - i) * 0.05
          
          return (
            <Card 
              key={i} 
              i={i}
              card={card}
              progress={scrollYProgress}
              range={[i * 0.3, 1]}
              targetScale={targetScale}
            />
          )
        })}
      </div>
    </div>
  )
}

function Card({ i, card, progress, range, targetScale }: any) {
  const containerRef = useRef(null)
  
  // Transform scale and y based on scroll progress
  const scale = useTransform(progress, range, [1, targetScale])
  const opacity = useTransform(progress, range, [1, 0.5])
  
  const Icon = card.icon

  return (
    <div ref={containerRef} className="h-[400px] flex items-center justify-center sticky top-[100px] w-full px-4 md:px-0">
      <motion.div 
        style={{ scale, top: `${i * 20}px`, opacity }}
        className={`relative flex flex-col justify-between w-full max-w-xl h-[300px] rounded-3xl p-8 md:p-10 transform origin-top shadow-[0_8px_32px_rgba(0,0,0,0.08)] border ${card.color}`}
      >
        <Icon className="w-10 h-10 opacity-70" strokeWidth={1.5} />
        <div>
          <h2 className="text-3xl md:text-4xl font-semibold tracking-tight mb-3">{card.title}</h2>
          <p className="text-lg md:text-xl opacity-80 font-medium">{card.description}</p>
        </div>
      </motion.div>
    </div>
  )
}
Save to favoritesCopy to Figma
React

Stacking Cards

Cards that dynamically stack on top of each other as you scroll down the page.

Installation

$ npx @cosmoo/oblivion add stacking-cards
Dependenciesnpm install framer-motion lucide-react

Usage Example

tsx
import StackingCards from "@/components/oblivion/stacking-cards"

export default function Demo() {
  return (
    <div className="p-10 flex justify-center">
      <StackingCards />
    </div>
  )
}