# Loader Dot Matrix

> A grid of dots that pulses in a ripple, wave, or rain pattern, shown four ways including inside a button

A shadcn/ui block from the [@cortexcn](https://www.cortexcn.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cortexcn/loader-dot-matrix-01).

- Page, with a live preview: https://shadcn.exchange/r/cortexcn/loader-dot-matrix-01
- Screenshot: https://shadcn.exchange/shot/cortexcn/loader-dot-matrix-01/light.png
- Registry item: https://ui.cortexcn.dev/r/loader-dot-matrix-01.json
- Listed: 2026-11-05, source last changed 2026-11-05

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/cortexcn/loader-dot-matrix-01.json
```

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

## Dependencies

- Registry items: `https://ui.cortexcn.dev/r/utils.json`, `button`

## Files

### `registry/blocks/loader-dot-matrix-01/components/loader-dot-matrix-demo.tsx`

```tsx
"use client";

import type { ReactNode } from "react";
import { Button } from "@/components/ui/button";

import { LoaderDotMatrix } from "./loader-dot-matrix";

// Cortexcn item "loader-dot-matrix-01", revision 8ec39664178b, MIT license.
// https://ui.cortexcn.dev/r/v/loader-dot-matrix-01/8ec39664178b.json

const EXAMPLES: { label: string; loader: ReactNode }[] = [
  { label: "Ripple (default)", loader: <LoaderDotMatrix /> },
  { label: "Wave", loader: <LoaderDotMatrix pattern="wave" /> },
  {
    label: "Rain, 6 by 6",
    loader: <LoaderDotMatrix cols={6} pattern="rain" rows={6} />,
  },
  {
    label: "In a button",
    loader: (
      <Button className="gap-2" disabled variant="outline">
        <LoaderDotMatrix cols={3} dotSize={2} rows={3} speed={1.2} />
        Saving
      </Button>
    ),
  },
];

/** The dot matrix loader four ways: its default, two other patterns, and in a button. */
export function LoaderDotMatrixDemo() {
  return (
    <div className="grid w-full max-w-3xl gap-3 sm:grid-cols-2">
      {EXAMPLES.map((example) => (
        <figure
          className="flex aspect-4/3 flex-col items-center justify-center gap-6 rounded-xl border bg-background p-6"
          key={example.label}
        >
          <div className="flex flex-1 items-center">{example.loader}</div>
          <figcaption className="font-mono text-muted-foreground text-xs">
            {example.label}
          </figcaption>
        </figure>
      ))}
    </div>
  );
}
```

### `registry/blocks/loader-dot-matrix-01/components/loader-dot-matrix.tsx`

```tsx
"use client";

import { useMemo } from "react";

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

// Cortexcn item "loader-dot-matrix-01", revision 8ec39664178b, MIT license.
// https://ui.cortexcn.dev/r/v/loader-dot-matrix-01/8ec39664178b.json

export interface 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: { row: number; col: number; delay: number }[] = [];
    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
      aria-label="Loading"
      aria-live="polite"
      className={cn("inline-flex", className)}
      data-slot="loader-dot-matrix"
    >
      <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(({ row, col, delay }) => (
          <span
            className="rounded-full bg-foreground will-change-transform"
            key={`${row}-${col}`}
            style={{
              width: `${dotSize}px`,
              height: `${dotSize}px`,
              animation: `dot-matrix-pulse ${speed}s ease-in-out infinite`,
              animationDelay: `${delay}s`,
            }}
          />
        ))}
      </div>
    </output>
  );
}
```
