my/ui

Command Palette

Search for a command to run...

All components

Card Stack

cards

Aceternity UI component.

responsive · 480px

Install

Same command in any shadcn project — React (Vite/CRA), Next.js, Remix, Astro, and more:

$npx shadcn@latest add https://your-domain/r/card-stack.json

Usage

"use client";
import { CardStack } from "@/registry/aceternity-ui/card-stack";

const CARDS = [
  {
    id: 0,
    name: "Alice Johnson",
    designation: "Senior Engineer at Vercel",
    content: (
      <p>
        These cards are absolutely gorgeous. The stacking animation is so smooth
        and the design is clean. I use them in all my projects now.
      </p>
    ),
  },
  {
    id: 1,
    name: "Bob Martinez",
    designation: "Product Designer at Figma",
    content: (
      <p>
        The attention to detail is incredible. Every pixel is perfectly placed
        and the interactions feel natural and delightful.
      </p>
    ),
  },
  {
    id: 2,
    name: "Carol Chen",
    designation: "CTO at Startup",
    content: (
      <p>
        We integrated these components into our design system and the team loves
        them. They save us so much time and always look professional.
      </p>
    ),
  },
];

export default function Demo() {
  return (
    <div className="flex items-center justify-center w-full py-20">
      <CardStack items={CARDS} />
    </div>
  );
}

Component source

"use client";
import { useEffect, useState } from "react";
import { motion } from "motion/react";

let interval: any;

type Card = {
  id: number;
  name: string;
  designation: string;
  content: React.ReactNode;
};

export const CardStack = ({
  items,
  offset,
  scaleFactor,
}: {
  items: Card[];
  offset?: number;
  scaleFactor?: number;
}) => {
  const CARD_OFFSET = offset || 10;
  const SCALE_FACTOR = scaleFactor || 0.06;
  const [cards, setCards] = useState<Card[]>(items);

  useEffect(() => {
    startFlipping();

    return () => clearInterval(interval);
  }, []);
  const startFlipping = () => {
    interval = setInterval(() => {
      setCards((prevCards: Card[]) => {
        const newArray = [...prevCards]; // create a copy of the array
        newArray.unshift(newArray.pop()!); // move the last element to the front
        return newArray;
      });
    }, 5000);
  };

  return (
    <div className="relative  h-60 w-60 md:h-60 md:w-96">
      {cards.map((card, index) => {
        return (
          <motion.div
            key={card.id}
            className="absolute dark:bg-black bg-white h-60 w-60 md:h-60 md:w-96 rounded-3xl p-4 shadow-xl border border-neutral-200 dark:border-white/[0.1]  shadow-black/[0.1] dark:shadow-white/[0.05] flex flex-col justify-between"
            style={{
              transformOrigin: "top center",
            }}
            animate={{
              top: index * -CARD_OFFSET,
              scale: 1 - index * SCALE_FACTOR, // decrease scale for cards that are behind
              zIndex: cards.length - index, //  decrease z-index for the cards that are behind
            }}
          >
            <div className="font-normal text-neutral-700 dark:text-neutral-200">
              {card.content}
            </div>
            <div>
              <p className="text-neutral-500 font-medium dark:text-white">
                {card.name}
              </p>
              <p className="text-neutral-400 font-normal dark:text-neutral-200">
                {card.designation}
              </p>
            </div>
          </motion.div>
        );
      })}
    </div>
  );
};

Dependencies

motion

Source: Aceternity UI