Motion reveal

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.

Pick an animation:
Simulate loading:
Loading cards
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>
  );
}

Loading UI

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