FreeCards
Card Stack
Use this component for testimonials, feature highlights, quotes, product tips, or small case-study snippets that should rotate without a heavy carousel.
ReactTailwind CSSMotionCards
I don't like messy handoffs, so we ship clean docs with every sprint.
Elena Moss
Product Founder
The component library helped us prototype three landing pages in one afternoon.
Ravi Singh
Growth Lead
Small UI details made the product feel much more premium than our first draft.
Maya Chen
Design Partner
Overview
An auto-rotating testimonial card stack that cycles content on an interval.
Framework
React
Styling
Tailwind CSS
Animation
Motion
Create a Vite React project
bashnpm create vite@latest my-ui-app -- --template react-ts
cd my-ui-app
npm installCreate a Next.js project
bashnpx create-next-app@latest my-ui-app --typescript --tailwind --eslint
cd my-ui-appAdd Tailwind CSS to Vite
bashnpm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -pInstall dependencies
bashnpm install framer-motionUsage
tsx<CardStack />Card Stack.tsx
tsx"use client";
import { useEffect, useState } from "react";
import { motion } from "framer-motion";
const cards = [
{
quote: "I don't like messy handoffs, so we ship clean docs with every sprint.",
name: "Elena Moss",
role: "Product Founder",
},
{
quote: "The component library helped us prototype three landing pages in one afternoon.",
name: "Ravi Singh",
role: "Growth Lead",
},
{
quote: "Small UI details made the product feel much more premium than our first draft.",
name: "Maya Chen",
role: "Design Partner",
},
];
export function CardStack() {
const [active, setActive] = useState(0);
useEffect(() => {
const timer = window.setInterval(
() => setActive((current) => (current + 1) % cards.length),
1800,
);
return () => window.clearInterval(timer);
}, []);
const orderedCards = [cards[active], ...cards.filter((_, index) => index !== active)];
return (
<div className="relative h-48 w-full max-w-md">
{orderedCards.map((card, index) => (
<motion.div
key={card.name}
animate={{ y: index * 10, scale: 1 - index * 0.04 }}
transition={{ duration: 0.4 }}
className="absolute inset-x-0 rounded-xl border border-slate-200 bg-white p-5 text-slate-900 shadow-2xl shadow-black/20"
style={{ zIndex: orderedCards.length - index }}
>
<p className="text-sm leading-relaxed">{card.quote}</p>
<p className="mt-6 text-sm font-bold">{card.name}</p>
<p className="text-xs text-slate-500">{card.role}</p>
</motion.div>
))}
</div>
);
}