Staggered Reveal Testimonial Grid
Original · freeA testimonial grid whose cards spring in with a staggered scroll reveal and light up with an animated gradient border on hover.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/atest-stagger-grid.json"use client";
import { motion, useReducedMotion } from "motion/react";
const reviews = [
{
quote:
"Six months in and I still open it every morning without a hint of dread. That alone puts it in a category of one for me.",
name: "Helena Vance",
role: "Design Lead, Everly",
initials: "HV",
tint: "from-emerald-500 to-teal-500",
rating: 5,
featured: true,
},
{
quote: "Onboarding that used to take a fortnight now takes an afternoon.",
name: "Marcus Bell",
role: "Ops, Kestrel",
initials: "MB",
tint: "from-teal-500 to-cyan-500",
rating: 5,
},
{
quote: "The support team answers like people who actually use the product.",
name: "Sofia Lindqvist",
role: "PM, Loomly",
initials: "SL",
tint: "from-cyan-500 to-sky-500",
rating: 5,
},
{
quote: "It quietly does the boring work so we can do the interesting work.",
name: "Tariq Hassan",
role: "CTO, Brightfold",
initials: "TH",
tint: "from-green-500 to-emerald-500",
rating: 5,
},
{
quote: "Rock-solid uptime and a roadmap that clearly listens to us.",
name: "Grace Okonkwo",
role: "Founder, Palisade",
initials: "GO",
tint: "from-emerald-500 to-green-500",
rating: 4,
},
{
quote:
"We compared five alternatives on a spreadsheet. This one won on every row that mattered, and on a few we did not expect to care about.",
name: "Julian Ross",
role: "Head of Growth, Meridian",
initials: "JR",
tint: "from-teal-500 to-emerald-500",
rating: 5,
},
];
export default function AtestStaggerGrid() {
const reduce = useReducedMotion();
const container = {
hidden: {},
show: { transition: { staggerChildren: 0.09, delayChildren: 0.05 } },
};
const item = reduce
? { hidden: { opacity: 0 }, show: { opacity: 1 } }
: {
hidden: { opacity: 0, y: 28, scale: 0.96 },
show: {
opacity: 1,
y: 0,
scale: 1,
transition: { type: "spring" as const, stiffness: 260, damping: 24 },
},
};
return (
<section className="bg-white px-6 py-20 dark:bg-zinc-950 md:py-28">
<style>{`
@keyframes atest-sg-spin { to { transform: rotate(1turn); } }
@keyframes atest-sg-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
.atest-sg-conic {
background: conic-gradient(from 0deg, transparent 0deg, #10b981 60deg, #06b6d4 140deg, #14b8a6 220deg, transparent 300deg);
animation: atest-sg-spin 4.5s linear infinite;
}
.atest-sg-shimmer {
background-image: linear-gradient(110deg, transparent 30%, rgba(16,185,129,0.12) 50%, transparent 70%);
background-size: 200% 100%;
animation: atest-sg-shimmer 3.5s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.atest-sg-conic, .atest-sg-shimmer { animation: none; }
}
`}</style>
<motion.div
initial={reduce ? false : { opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.6 }}
transition={{ duration: 0.5, ease: "easeOut" }}
className="mx-auto max-w-2xl text-center"
>
<p className="text-sm font-semibold uppercase tracking-[0.2em] text-emerald-600 dark:text-emerald-400">
Testimonials
</p>
<h2 className="mt-3 text-3xl font-bold tracking-tight text-balance text-zinc-900 sm:text-4xl dark:text-white">
The proof turns up in the details
</h2>
<p className="mt-4 text-lg text-zinc-600 dark:text-zinc-400">
Scroll a little and watch what teams say once the novelty has worn off.
</p>
</motion.div>
<motion.ul
variants={container}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.15 }}
className="mx-auto mt-14 grid max-w-6xl auto-rows-fr grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3"
>
{reviews.map((r) => (
<motion.li
key={r.name}
variants={item}
whileHover={reduce ? undefined : { y: -6 }}
transition={{ type: "spring", stiffness: 300, damping: 22 }}
className={`group relative overflow-hidden rounded-2xl p-px ${
r.featured ? "sm:col-span-2 lg:col-span-2" : ""
}`}
>
{/* Animated gradient ring on hover */}
<div
aria-hidden="true"
className="atest-sg-conic pointer-events-none absolute inset-[-120%] opacity-0 transition-opacity duration-500 group-hover:opacity-100"
/>
<div className="relative flex h-full flex-col overflow-hidden rounded-[15px] border border-zinc-200 bg-zinc-50 p-6 dark:border-zinc-800 dark:bg-zinc-900">
{r.featured && (
<div
aria-hidden="true"
className="atest-sg-shimmer pointer-events-none absolute inset-0"
/>
)}
<div className="relative flex items-center justify-between">
<div className="flex items-center gap-0.5" aria-label={`Rated ${r.rating} out of 5`}>
{[0, 1, 2, 3, 4].map((s) => (
<svg
key={s}
viewBox="0 0 24 24"
className={
s < r.rating ? "h-4 w-4 fill-amber-400" : "h-4 w-4 fill-zinc-300 dark:fill-zinc-700"
}
aria-hidden="true"
>
<path d="M11.48 3.5a.56.56 0 011.04 0l2.12 5.11a.56.56 0 00.48.35l5.52.44c.5.04.7.66.32.99l-4.2 3.6a.56.56 0 00-.19.56l1.29 5.38a.56.56 0 01-.84.61l-4.73-2.88a.56.56 0 00-.58 0l-4.73 2.88a.56.56 0 01-.84-.61l1.29-5.38a.56.56 0 00-.19-.56l-4.2-3.6a.56.56 0 01.32-.99l5.52-.44a.56.56 0 00.48-.35L11.48 3.5z" />
</svg>
))}
</div>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
className="h-6 w-6 fill-emerald-500/15 text-emerald-500/40"
>
<path d="M9.5 6C6.5 7 5 9.5 5 13v5h6v-6H8c0-2 1-3.2 3-3.7L9.5 6zm9 0c-3 1-4.5 3.5-4.5 7v5h6v-6h-3c0-2 1-3.2 3-3.7L18.5 6z" />
</svg>
</div>
<blockquote
className={`relative mt-4 flex-1 text-pretty text-zinc-700 dark:text-zinc-300 ${
r.featured ? "text-lg font-medium sm:text-xl" : "text-[15px]"
}`}
>
{r.quote}
</blockquote>
<figcaption className="relative mt-6 flex items-center gap-3">
<span
className={`grid h-10 w-10 shrink-0 place-items-center rounded-full bg-gradient-to-br ${r.tint} text-xs font-semibold text-white shadow-md`}
aria-hidden="true"
>
{r.initials}
</span>
<span className="min-w-0">
<span className="block truncate text-sm font-semibold text-zinc-900 dark:text-white">
{r.name}
</span>
<span className="block truncate text-xs text-zinc-500 dark:text-zinc-400">
{r.role}
</span>
</span>
</figcaption>
</div>
</motion.li>
))}
</motion.ul>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Three Card Testimonial Grid
OriginalA clean three-column grid of testimonial cards, each with a star rating, quote and an initial avatar with name and role.

Single Featured Quote
OriginalA large centred pull quote with a decorative quotation mark, five-star rating and a prominent author avatar for a single standout testimonial.

Logo Wall Marquee
OriginalA pair of infinitely scrolling, pause-on-hover marquees pairing a customer logo wall with compact quote cards, with reduced-motion support.

Masonry Testimonial Wall
OriginalA CSS-columns masonry wall of testimonial cards in varied lengths, each with a star rating and initial avatar for a natural, organic layout.

Aggregate Rating Spotlight
OriginalA split layout pairing a large aggregate rating score, star row and overlapping avatar stack with two featured review cards.

Auto-Rotating Quote Carousel
OriginalAn auto-advancing testimonial carousel that slides and crossfades between quotes with a live progress bar, pause-on-hover, and dot plus arrow navigation.

Infinite Marquee Testimonial Wall
OriginalA three-row marquee wall of review cards scrolling in alternating directions that pause on hover, with a shimmering gradient headline and faded edges.

Spotlight Hero
OriginalA centred hero with a soft radial spotlight, badge and dual call-to-action.

Split Hero
OriginalA two-column hero pairing a headline and CTAs with a product mock and social proof.

Gradient Spotlight Hero
OriginalA minimal centred hero with a soft gradient-mesh backdrop, announcement pill and dual call-to-action buttons.

App Preview Hero
OriginalA centred hero that pairs headline copy with a realistic product dashboard mock built entirely from markup, complete with browser chrome and a floating notification card.

Waitlist Capture Hero
OriginalA dark, focused hero with an inline email capture form and avatar social proof, ready for pre-launch waitlists.

