Card 1
Card description
Cards animate in when they scroll into view. Each card staggers slightly using
MotionReveal.
Pick an animation style below. Cards load with skeletons on first visit. Toggle Simulate loading to show or hide the skeleton UI — On keeps skeletons visible, Off shows the cards again.
import { MotionReveal } from '@/animations';
import { Card } from '@/ui/components/card';
const MOTION_REVEAL_STAGGER_MS = 100;
function CardGrid() {
return (
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{cards.map((image, index) => (
<MotionReveal
key={`${index}-${animation}`}
animation={animation}
delay={index * MOTION_REVEAL_STAGGER_MS}
>
<Card
title={`Card ${index + 1}`}
description="Card description"
media={image}
/>
</MotionReveal>
))}
</div>
);
}Wrap the grid in Suspense and use CardSkeleton as the fallback while data
loads. The demo uses the same markup for Simulate loading and the
Suspense fallback.
import { Suspense } from 'react';
import { CardSkeleton } from '@/ui/components/card-skeleton';
import { placeholderImageList } from '@/assets';
<Suspense
fallback={
<div
className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"
role="status"
aria-busy="true"
aria-live="polite"
>
<span className="sr-only">Loading cards</span>
{placeholderImageList.map((_, index) => (
<CardSkeleton key={index} />
))}
</div>
}
>
<CardGrid />
</Suspense>Next.js docs: Loading UI and Streaming