Docs
Go back
Component by
OV
Oblivion1. 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>
)
}"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-cardsDependencies
npm install framer-motion lucide-reactUsage Example
tsx
import StackingCards from "@/components/oblivion/stacking-cards"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<StackingCards />
</div>
)
}import StackingCards from "@/components/oblivion/stacking-cards"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<StackingCards />
</div>
)
}