Drag Card Stack
It is a high-end, interactive UI component built with React and Framer Motion that mimics the tactile experience of a physical deck of cards. The core functionality relies on a physics-based drag engine: users can flick the top card off the stack, which triggers a state-reordering logic that moves the "dismissed" card to the bottom of the pile, enabling an infinite, cyclic loop of content.
Card 1
Swipe left or right to dismiss this card and reveal the next task in your stack.
Card 2
Swipe left or right to dismiss this card and reveal the next task in your stack.
Card 3
Swipe left or right to dismiss this card and reveal the next task in your stack.
Card 4
Swipe left or right to dismiss this card and reveal the next task in your stack.
Dependencies
framer-motionCode
"use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
const DragCardStack = () => {
// replace it by your data
const [cards, setCards] = useState([
{
id: 1,
color: "#FF0055",
text: "Card 1",
bgimg:
"url('https://images.unsplash.com/vector-1759400775660-825488f4f480?q=80&w=2128&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')",
},
{
id: 2,
color: "#0099FF",
text: "Card 2",
bgimg:
"url('https://images.unsplash.com/vector-1738399643589-0d4500d75b38?q=80&w=2020&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')",
},
{
id: 3,
color: "#22CC88",
text: "Card 3",
bgimg: "url('/myimg3-new.png')",
},
{
id: 4,
color: "#FF9900",
text: "Card 4",
bgimg: "url('/myimg2.jpg')",
},
]);
const moveToEnd = (fromIndex: any) => {
setCards((prevCards) => {
const newCards = [...prevCards];
const item = newCards.splice(fromIndex, 1)[0];
newCards.unshift(item);
return newCards;
});
};
return (
<div className="relative h-120 flex justify-center items-center">
<AnimatePresence>
{cards.map((card, index) => {
const isTop = index === cards.length - 1;
return (
<motion.div
className="border-10 rounded-3xl overflow-hidden absolute flex h-[400px] w-[300px] cursor-grab items-center justify-center shadow-[0_10px_20px_rgba(0,0,0,0.2)]"
key={card.id}
style={{
borderColor: card.color,
zIndex: index,
}}
drag={isTop ? "x" : false}
dragConstraints={{ left: 0, right: 0 }}
onDragEnd={(event, info) => {
if (Math.abs(info.offset.x) > 100) {
moveToEnd(index);
}
}}
initial={{ scale: 0.9, opacity: 0 }}
animate={{
scale: 1 - (cards.length - 1 - index) * 0.05,
y: (cards.length - 1 - index) * -15,
opacity: 1,
}}
exit={{ scale: 0.8, opacity: 0 }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
whileDrag={{ scale: 1.05 }}
>
<motion.div
style={{
backgroundImage: card.bgimg,
}}
className="relative h-full w-full flex flex-col justify-between overflow-hidden p-6 text-white box-border bg-cover bg-center bg-no-repeat relative"
>
<div className="absolute inset-0 h-full w-full bg-gradient-to-t from-black/80 via-black/60 to-transparent" />
<div className="flex justify-between items-center z-10">
<span className="text-[10px] font-extrabold tracking-[1.2px] bg-white/20 px-2 py-1 rounded-[6px]">
FEATURED
</span>
<div
style={{ backgroundColor: card.color }}
className="h-2 w-2 rounded-full"
/>
</div>
<div className="z-10 text-left">
<h2 className="mb-2 text-[28px] font-bold">{card.text}</h2>
<p className="m-0 text-sm leading-normal opacity-90">
Swipe left or right to dismiss this card and reveal the next
task in your stack.
</p>
</div>
<div className="flex items-center justify-between border-t border-white/20 pt-4 z-10">
<div className="flex flex-col">
<span style={{ fontSize: "12px", opacity: 0.8 }}>
Priority
</span>
<span style={{ fontWeight: "bold" }}>High</span>
</div>
<button
className="cursor-pointer rounded-[10px] bg-white px-4 py-2 text-[12px] font-semibold text-black border-none"
onClick={(e) => e.stopPropagation()}
>
View Details
</button>
</div>
</motion.div>
</motion.div>
);
})}
</AnimatePresence>
</div>
);
};
export default DragCardStack;
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| cards | CardItem[] | initialCards | An array of objects containing the id, color, text, and bgimg for each card in the stack. |
Drag Card Stack Two
A sleek, interactive card stack component built with React, Tailwind CSS, and Framer Motion. It features a modern glassmorphic design and intuitive drag-and-drop mechanics that let users swipe through a layered stack of cards.
#Card 1
#Card 2
#Card 3
#Card 4
Dependencies
framer-motionUtils
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Code
"use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { cn } from "@/lib/utils";
interface CardProps {
card: {
id: number;
color: string;
text: string;
bgimg: string;
className?: string;
};
index: number;
totalCards: number;
moveToEnd: (fromIndex: number) => void;
}
const Card = ({ card, index, totalCards, moveToEnd }: CardProps) => {
const isTop = index === totalCards - 1;
return (
<motion.div
className={cn(
"overflow-hidden bg-white absolute flex cursor-grab items-center justify-center border-8 select-none",
!card.className ? "h-[420px] w-[300px]" : card.className,
)}
style={{
borderColor: card.color,
zIndex: index,
boxShadow: `0 15px 35px -10px rgba(0,0,0,0.15), inset 0 2px 14px ${card.color}25`,
}}
drag={isTop ? true : false}
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
onDragEnd={(event, info) => {
if (Math.abs(info.offset.x) > 100 || Math.abs(info.offset.y) > 100) {
moveToEnd(index);
}
}}
initial={{ scale: 0.85, opacity: 0, y: 20 }}
animate={{
scale: 1 - (totalCards - 1 - index) * 0.05,
y: (totalCards - 1 - index) * -16,
opacity: 1,
}}
exit={{ scale: 0.8, opacity: 0 }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
whileHover={isTop ? { scale: 1.02, y: -10 } : {}}
whileDrag={{
scale: 1.04,
boxShadow: "0 25px 50px -12px rgba(0,0,0,0.25)",
}}
>
<div className="relative h-full w-full flex flex-col justify-between p-6 backdrop-blur-sm">
<div
style={{
backgroundImage: card.bgimg,
}}
className="w-full shadow-[inset_0_4px_12px_rgba(0,0,0,0.3)] h-[400px] bg-cover bg-center bg-no-repeat z-10 border border-slate-200/50"
/>
<h2 className="text-2xl font-black italic text-center text-slate-800 z-10 tracking-wide mt-4 uppercase">
#{card.text}
</h2>
<div
className="absolute inset-0 z-0 opacity-15 pointer-events-none rounded-3xl"
style={{
backgroundImage: `
repeating-linear-gradient(
45deg,
${card.color},
${card.color} 1px,
transparent 1px,
transparent 5px
),
repeating-linear-gradient(
-45deg,
${card.color},
${card.color} 1px,
transparent 1px,
transparent 5px
)
`,
}}
/>
</div>
</motion.div>
);
};
const DragCardStackTwo = () => {
const [cards, setCards] = useState([
{
id: 1,
className: "w-[400px] h-[400px] rotate-15",
color: "#FF0055",
text: "Card 1",
bgimg:
"url('https://images.unsplash.com/photo-1777131263706-d6c57f590a7c?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')",
},
{
id: 2,
className: "w-[400px] h-[400px] -rotate-15",
color: "#0099FF",
text: "Card 2",
bgimg:
"url('https://images.unsplash.com/photo-1777026050794-a5e4ef7cd254?q=80&w=2071&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')",
},
{
id: 3,
className: "w-[400px] h-[400px] rotate-30",
color: "#22CC88",
text: "Card 3",
bgimg:
"url('https://images.unsplash.com/photo-1777067165450-60573b8afd54?q=80&w=1936&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')",
},
{
id: 4,
className: "w-[400px] h-[400px] -rotate-30",
color: "#FF9900",
text: "Card 4",
bgimg:
"url('https://images.unsplash.com/photo-1776941654802-f29f34a675b9?q=80&w=1972&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')",
},
]);
const moveToEnd = (fromIndex: number) => {
setCards((prevCards) => {
const newCards = [...prevCards];
const item = newCards.splice(fromIndex, 1)[0];
newCards.unshift(item);
return newCards;
});
};
return (
<div className="relative h-[550px] flex justify-center items-center w-full rounded-3xl p-10">
<AnimatePresence>
{cards.map((card, index) => (
<Card
key={card.id}
card={card}
index={index}
totalCards={cards.length}
moveToEnd={moveToEnd}
/>
))}
</AnimatePresence>
</div>
);
};
export default DragCardStackTwo;
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| cards | CardItem[] | initialCards | An array of objects containing the id, color, text, className and bgimg for each card in the stack. |