# Dot Matrix Loader

> A grid of dots with ripple, wave, and cascade animation effects

A shadcn/ui block from the [@elements](https://www.tryelements.dev) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/elements/loader-dot-matrix).

- Page, with a live preview: https://shadcn.exchange/r/elements/loader-dot-matrix
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/elements/loader-dot-matrix-light-9974bdee6ac74540.png
- Registry item: https://www.tryelements.dev/r/loader-dot-matrix.json
- Categories: loaders
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://www.tryelements.dev/r/loader-dot-matrix.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Notes from the registry

Dot grid loader with configurable rows, columns, dot size, and animation patterns (ripple, wave, rain). CSS-only animations with computed stagger delays.

## Files

### `registry/default/blocks/loaders/loader-dot-matrix/components/elements/loader-dot-matrix.tsx`

```tsx
"use client";

import { useMemo } from "react";

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

export type LoaderDotMatrixProps = {
  rows?: number;
  cols?: number;
  pattern?: "ripple" | "wave" | "rain";
  speed?: number;
  dotSize?: number;
  className?: string;
};

export function LoaderDotMatrix({
  rows = 5,
  cols = 7,
  pattern = "ripple",
  speed = 1.5,
  dotSize = 3,
  className,
}: LoaderDotMatrixProps) {
  const dots = useMemo(() => {
    const result = [];
    const centerX = (cols - 1) / 2;
    const centerY = (rows - 1) / 2;
    const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);

    for (let row = 0; row < rows; row++) {
      for (let col = 0; col < cols; col++) {
        let delay = 0;

        if (pattern === "ripple") {
          const distance = Math.sqrt(
            (col - centerX) ** 2 + (row - centerY) ** 2,
          );
          delay = (distance / maxDist) * speed;
        } else if (pattern === "wave") {
          delay = ((row + col) / (rows + cols - 2)) * speed;
        } else if (pattern === "rain") {
          delay = row * 0.1 + (col / cols) * speed;
        }

        result.push({ row, col, delay });
      }
    }

    return result;
  }, [rows, cols, pattern, speed]);

  const gap = dotSize * 1.5;

  return (
    <output
      data-slot="loader-dot-matrix"
      aria-live="polite"
      aria-label="Loading"
      className={cn("inline-flex", className)}
    >
      <style>
        {`
          @keyframes dot-matrix-pulse {
            0%, 100% { opacity: 0.15; transform: scale(0.3); }
            50% { opacity: 1; transform: scale(1); }
          }
        `}
      </style>
      <span className="sr-only">Loading</span>
      <div
        style={{
          display: "grid",
          gridTemplateColumns: `repeat(${cols}, 1fr)`,
          gap: `${gap}px`,
        }}
      >
        {dots.map(({ delay }, index) => (
          <span
            // biome-ignore lint/suspicious/noArrayIndexKey: stable list derived from fixed grid dimensions
            key={index}
            className="rounded-full bg-foreground will-change-transform"
            style={{
              width: `${dotSize}px`,
              height: `${dotSize}px`,
              animation: `dot-matrix-pulse ${speed}s ease-in-out infinite`,
              animationDelay: `${delay}s`,
            }}
          />
        ))}
      </div>
    </output>
  );
}
```
