Card Stack
Cards stack on top of each other after some interval. Perfect for showing testimonials.
These cards are amazing, I want to use them in my project. Framer motion is a godsend ngl tbh fam 🙏
Manu Arora
Senior Software Engineer
I dont like this Twitter thing, deleting it right away because yolo. Instead, I would like to call it X.com so that it can easily be confused with adult sites.
Elon Musk
Senior Shitposter
The first rule of Fight Club is that you do not talk about fight club. The second rule of Fight club is that you DO NOT TALK about fight club.
Tyler Durden
Manager Project Mayhem
<script>
import { CardStack } from '.';
const CARDS = [
{
id: 0,
name: 'Manu Arora',
designation: 'Senior Software Engineer',
content:
"These cards are amazing, <span class='bg-emerald-100 px-1 py-0.5 font-bold text-emerald-700 dark:bg-emerald-700/[0.2] dark:text-emerald-500'>I want to use them</span> in my project. Framer motion is a godsend ngl tbh fam 🙏"
},
{
id: 1,
name: 'Elon Musk',
designation: 'Senior Shitposter',
content:
"I dont like this Twitter thing, <span class='bg-emerald-100 px-1 py-0.5 font-bold text-emerald-700 dark:bg-emerald-700/[0.2] dark:text-emerald-500'>deleting it right away</span> because yolo. Instead, I would like to call it <span class='bg-emerald-100 px-1 py-0.5 font-bold text-emerald-700 dark:bg-emerald-700/[0.2] dark:text-emerald-500'>X.com</span> so that it can easily be confused with adult sites."
},
{
id: 2,
name: 'Tyler Durden',
designation: 'Manager Project Mayhem',
content:
"The first rule of <span class='bg-emerald-100 px-1 py-0.5 font-bold text-emerald-700 dark:bg-emerald-700/[0.2] dark:text-emerald-500'>Fight Club</span> is that you do not talk about fight club. The second rule of <span class='bg-emerald-100 px-1 py-0.5 font-bold text-emerald-700 dark:bg-emerald-700/[0.2] dark:text-emerald-500'>Fight club</span> is that you DO NOT TALK about fight club."
}
];
</script>
<div class="flex w-full items-center justify-center">
<CardStack items={CARDS} />
</div>
Installation
Install Dependencies
npm i svelte-motion clsx tailwind-merge
Add util file
src/lib/utils/cn.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Copy the source code
src/lib/components/ui/CardStack/CardStack.svelte
<script lang="ts">
import { onMount } from 'svelte';
import { Motion } from 'svelte-motion';
type Card = {
id: number;
name: string;
designation: string;
content: string;
};
export let items: Card[];
export let offset: number | undefined = undefined;
export let scaleFactor: number | undefined = undefined;
let interval: any;
const CARD_OFFSET = offset || 16;
const SCALE_FACTOR = scaleFactor || 0.06;
let cards: Card[] = items;
onMount(() => {
startFlipping();
});
const startFlipping = () => {
interval = setInterval(() => {
const newArray = [...cards]; // create a copy of the array
newArray.unshift(newArray.pop()!); // move the last element to the front
cards = newArray;
}, 5000);
return () => clearInterval(interval);
};
</script>
<div class="relative h-60 w-60 md:h-60 md:w-96">
{#each cards as card, index (card.id)}
<Motion
let:motion
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
use:motion
class="absolute flex h-60 w-60 flex-col justify-between rounded-3xl border border-neutral-200 bg-white p-4 shadow-xl shadow-black/[0.1] dark:border-white/[0.1] dark:bg-black dark:shadow-white/[0.05] md:h-60 md:w-96"
>
<div class="font-normal text-neutral-700 dark:text-neutral-200">
{@html card.content}
</div>
<div>
<p class="font-medium text-neutral-500 dark:text-white">
{card.name}
</p>
<p class="font-normal text-neutral-400 dark:text-neutral-200">
{card.designation}
</p>
</div>
</div>
</Motion>
{/each}
</div>
<style>
.highlight {
@apply bg-emerald-100 px-1 py-0.5 font-bold text-emerald-700 dark:bg-emerald-700/[0.2] dark:text-emerald-500;
}
</style>
src/lib/components/ui/CardStack/index.ts
import CardStack from './CardStack.svelte';
export { CardStack };
Props
CardStack
Prop | Type | Description |
---|---|---|
className | string | undefined | The class name of the child component. |
items | Array<{id: number; name: string; designation: string; content: HTMLElement;}> | The cards that you want to render in the stack effect |
offset | number | undefined | The offset by which you want the cards to stack |
scaleFactor | number | undefined | The scale by which you want the cards to stack |