Skip to content
All Taste components

Data display

Skeleton loading

Grey placeholders in the shape of the content, shown while it loads.

card · list rows · shimmer

Loading
import { cn } from "@/lib/utils";

/** Shimmering placeholder; add the shimmer keyframes below to your globals.css. */
export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      aria-hidden
      className={cn(
        "rounded-sm bg-[linear-gradient(90deg,var(--tst-surface-2)_0%,var(--tst-surface)_50%,var(--tst-surface-2)_100%)] bg-[length:200%_100%] animate-[shimmer_1.4s_linear_infinite] motion-reduce:animate-none",
        className,
      )}
      {...props}
    />
  );
}

// globals.css:  @keyframes shimmer { to { background-position: -200% 0; } }

export function CardSkeleton() {
  return (
    <div role="status" aria-busy="true" className="grid gap-3 rounded-lg border border-border bg-bg p-4">
      <span className="sr-only">Loading</span>
      <Skeleton className="h-28 rounded" />
      <Skeleton className="h-3.5 w-[70%]" />
      <Skeleton className="h-2.5 w-[95%]" />
      <Skeleton className="h-2.5 w-[55%]" />
      <div className="mt-1 flex justify-between">
        <Skeleton className="h-[22px] w-16 rounded-full" />
        <Skeleton className="h-[30px] w-20 rounded" />
      </div>
    </div>
  );
}

export function ListSkeleton({ rows = 4 }: { rows?: number }) {
  return (
    <div role="status" aria-busy="true" className="grid gap-4">
      <span className="sr-only">Loading</span>
      {Array.from({ length: rows }, (_, i) => (
        <div key={i} className="flex items-center gap-3">
          <Skeleton className="size-8 shrink-0 rounded-full" />
          <div className="grid flex-1 gap-1.5">
            <Skeleton className="h-2.5 w-3/4" />
            <Skeleton className="h-2 w-2/5" />
          </div>
        </div>
      ))}
    </div>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.