Skeleton Loading Grid

A shimmer skeleton placeholder for a grid of metric cards. Each card pulses with Tailwind's built-in animate-pulse, mimicking the real layout — label line, value, chart area, trend line — so the transition to loaded content feels seamless.

Dashboard StatsInspired by Linear
Live PreviewInteractive
Linear · Dashboard

Toggle between skeleton and loaded state · Shimmer uses Tailwind animate-pulse

SkeletonGrid.tsx
// Dependencies: react ^18
import React from 'react';

type Props = {
  count?: number;
  columns?: 2 | 3 | 4;
};

const colClass: Record<number, string> = {
  2: 'grid-cols-2',
  3: 'grid-cols-3',
  4: 'grid-cols-4',
};

export function SkeletonGrid({ count = 4, columns = 2 }: Props) {
  return (
    <div className={`grid gap-3 ${colClass[columns]}`}>
      {Array.from({ length: count }).map((_, i) => (
        <SkeletonCard key={i} />
      ))}
    </div>
  );
}

function SkeletonCard() {
  return (
    <div className="flex flex-col gap-3 rounded-xl border border-[#1a1a1a] bg-[#0d0d0d] p-4">
      <div className="h-3 w-24 rounded-md bg-[#1e1e1e] animate-pulse" />
      <div className="h-8 w-28 rounded-md bg-[#1e1e1e] animate-pulse" />
      <div className="h-3 w-16 rounded-md bg-[#1e1e1e] animate-pulse" />
    </div>
  );
}

// Usage:
// Render <SkeletonGrid count={4} columns={2} /> while your data is loading.
// Replace it with your real grid once the fetch resolves:
//
// {loading ? (
//   <SkeletonGrid count={4} columns={2} />
// ) : (
//   <MetricGrid data={metrics} />
// )}