Back to Discover
CardsIntermediate
Swipeable Stack
A stack of cards that you can swipe away, built with Framer Motion drag.
Live Preview
1
Next card
2
Next card
3
Swipe left or right
"use client";
import { useState } from "react";
import { motion } from "framer-motion";
export function SwipeableStack() {
const [cards, setCards] = useState([1, 2, 3]);
return (
<div className="flex flex-col items-center justify-center w-full h-[400px] bg-[#0a0a0a] rounded-xl border border-border overflow-hidden p-6">
<div className="relative w-48 h-64 mt-4">
{cards.map((card, index) => {
const isTop = index === cards.length - 1;
return (
<motion.div
key={card}
drag={isTop ? "x" : false}
dragConstraints={{ left: 0, right: 0 }}
dragElastic={1}
onDragEnd={(e, { offset, velocity }) => {
const swipe = Math.abs(offset.x) * velocity.x;
if (swipe < -10000 || swipe > 10000) {
setCards((prev) => prev.filter((c) => c !== card));
}
}}
className="absolute inset-0 flex flex-col items-center justify-center bg-[#151515] rounded-2xl border border-border shadow-xl origin-bottom cursor-grab active:cursor-grabbing"
style={{
zIndex: index,
}}
animate={{
scale: 1 - (cards.length - 1 - index) * 0.05,
y: (cards.length - 1 - index) * -10,
}}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
>
<span className="text-4xl font-bold text-muted-foreground select-none">{card}</span>
<p className="text-xs text-muted-foreground mt-4 text-center px-4 select-none">
{isTop ? "Swipe left or right" : "Next card"}
</p>
</motion.div>
);
})}
{cards.length === 0 && (
<div className="absolute inset-0 flex flex-col items-center justify-center">
<span className="text-sm text-muted-foreground mb-4">All done!</span>
<button
onClick={() => setCards([1, 2, 3])}
className="px-4 py-2 text-xs font-medium rounded-md bg-foreground text-background"
>
Reset Stack
</button>
</div>
)}
</div>
</div>
);
}