Loaders, AI & Widgetsv0.1.0
Matrix Dot Loader
A 4×4 dot matrix pulsing in patterns.
Installation
CLI
npx shadcn@latest add https://hantui-theta.vercel.app/r/matrix-loader.jsonManual
Install the dependencies
npm install clsx motion tailwind-mergeAdd the shared helpers
Components use
cn()fromlib/utils.ts. See Installation if you have not set them up yet.Copy the source
components/ui/matrix-loader/matrix-loader.tsx"use client"; import type { ComponentProps } from "react"; import { motion, useReducedMotion } from "motion/react"; import { cn } from "@/lib/utils"; export type MatrixPattern = "wave" | "diagonal" | "spiral" | "pulse" | "rain"; export interface MatrixLoaderProps extends Omit<ComponentProps<"span">, "children" | "onAnimationStart" | "onDrag" | "onDragStart" | "onDragEnd"> { /** The order in which dots light up. @default "wave" */ pattern?: MatrixPattern; /** Dot size in px. @default 5 */ dotSize?: number; /** Space between dots in px. @default 3 */ gap?: number; /** Seconds for one full cycle. @default 1.4 */ cycle?: number; /** Accessible label. @default "Loading" */ label?: string; } // Clockwise spiral from the top-left corner inward. const SPIRAL = [0, 1, 2, 3, 11, 12, 13, 4, 10, 15, 14, 5, 9, 8, 7, 6]; const RAIN = [2, 0, 3, 1]; /** Each dot's position in the sequence, from 0 to 1. */ function order(pattern: MatrixPattern, r: number, c: number) { switch (pattern) { case "diagonal": return (r + c) / 6; case "spiral": return SPIRAL[r * 4 + c] / 15; case "pulse": return Math.max(Math.abs(r - 1.5), Math.abs(c - 1.5)) > 1 ? 1 : 0; case "rain": return ((r + RAIN[c]) % 4) / 3; default: return c / 3; } } export function MatrixLoader({ pattern = "wave", dotSize = 5, gap = 3, cycle = 1.4, label = "Loading", className, ...props }: MatrixLoaderProps) { const reduceMotion = useReducedMotion(); return ( <span role="status" aria-label={label} className={cn("inline-grid grid-cols-4", className)} style={{ gap, gridTemplateColumns: `repeat(4, ${dotSize}px)` }} {...props} > {Array.from({ length: 16 }, (_, i) => { const delay = order(pattern, Math.floor(i / 4), i % 4) * cycle * 0.55; return ( <motion.span key={i} aria-hidden="true" className="block rounded-full bg-current" style={{ width: dotSize, height: dotSize }} initial={{ opacity: 0.15, scale: reduceMotion ? 1 : 0.7 }} animate={reduceMotion ? { opacity: [0.2, 0.75, 0.2] } : { opacity: [0.15, 1, 0.15], scale: [0.7, 1, 0.7] }} transition={{ duration: reduceMotion ? cycle * 1.6 : cycle, delay, repeat: Infinity, ease: "easeInOut" }} /> ); })} </span> ); }
Usage
import { MatrixLoader } from "@/components/ui/matrix-loader/matrix-loader";
<MatrixLoader pattern="spiral" label="Generating image" />API reference
| Prop | Type | Default | Description |
|---|---|---|---|
pattern | MatrixPattern | "wave" | The order in which dots light up. |
dotSize | number | 5 | Dot size in px. |
gap | number | 3 | Space between dots in px. |
cycle | number | 1.4 | Seconds for one full cycle. |
label | string | "Loading" | Accessible label. |
Also accepts every native <span> attribute, including ref and className.
- Sixteen dots light up in one of five orders: wave, diagonal, spiral, pulse or rain. Only opacity and scale animate.
- Uses currentColor; size it with dotSize and gap.
Accessibility
- A status element with an accessible label (default “Loading”); the dots are aria-hidden.
- With reduced motion enabled, dots fade slowly instead of scaling.