Concentric Rings Spinner
shadcn-extras/concentric-rings-spinnerFreeComponent
Dotted rings spinner (SVG). Customizable rings, dots, gap, taper, speed, and direction.
Install it
npx shadcn@latest add https://raw.githubusercontent.com/NayanDevLab/shadcn-extras/dev/public/c/concentric-rings-spinner.jsonSource
1'use client';23import * as React from 'react';4import { cn } from '@/lib/utils';56type RadiusShape = number | { inner: number; outer: number };78export type ConcentricRingsSpinnerProps = {9 /** Canvas size in px (square) */10 size?: number;11 /** Number of rings */12 rings?: number;13 /** Dots per ring (constant or per-ring taper if you pass a function) */14 dotsPerRing?: number;15 /** Radius for first (inner) ring */16 innerRadius?: number;17 /** Space between rings */18 ringGap?: number;19 /** Dot radius. Number for fixed or {inner, outer} to taper */20 dotRadius?: RadiusShape;21 /** Seconds per revolution for middle ring */22 speed?: number;23 /** Spin direction: 1 (cw) or -1 (ccw) */24 direction?: 1 | -1;25 /** Fade the center (0..1). 0=no fade, 1=strong fade */26 fadeCenter?: number;27 /** Fade the edges (0..1). 0=no fade, 1=strong fade */28 fadeEdge?: number;29 /** Whether to alternately reverse rings (nice parallax) */30 alternate?: boolean;31 /** Optional pulsing opacity */32 pulse?: boolean;33 /** Extra classes */34 className?: string;35 /** Extra classes for each dot */36 dotClassName?: string;37 /** a11y label */38 label?: string;39};4041function lerp(a: number, b: number, t: number) {42 return a + (b - a) * t;43}4445export function ConcentricRingsSpinner({46 size = 200,47 rings = 12,48 dotsPerRing = 36,49 innerRadius = 10,50 ringGap = 8,51 dotRadius = { inner: 2, outer: 4 },52 speed = 2,53 direction = 1,54 fadeCenter = 0.15,55 fadeEdge = 0.35,56 alternate = true,57 pulse = true,58 className,59 dotClassName,60 label = 'Loading…',61}: ConcentricRingsSpinnerProps) {62 const w = size;63 const h = size;64 const cx = w / 2;65 const cy = h / 2;6667 const getDotR = (ringIdx: number) => {68 if (typeof dotRadius === 'number') return dotRadius;69 // ringIdx: 0 (inner) -> rings-1 (outer)70 const t = rings <= 1 ? 0 : ringIdx / (rings - 1);71 return lerp(dotRadius.inner, dotRadius.outer, t);72 };7374 // ring opacity falloff75 const opacityFor = (ringIdx: number) => {76 const t = rings <= 1 ? 0 : ringIdx / (rings - 1);77 const centerFade = lerp(1, 1 - fadeCenter, 1 - Math.abs(0.5 - t) * 2); // higher in middle78 const edgeFade = lerp(1, 1 - fadeEdge, t); // fade outer79 return Math.max(0, Math.min(1, centerFade * edgeFade));80 };8182 return (83 <svg84 role='status'85 aria-label={label}86 width={w}87 height={h}88 viewBox={`0 0 ${w} ${h}`}89 className={cn('inline-block text-current', className)}90 >91 <style>{`92// … truncated
Files it writes
More from shadcn-extras
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Image Gridanimated-image-grid | shadcn-extras | Components | Free | no deps · 3 files | |
| Blog Card Fourblog-card-four | shadcn-extras | Components | Free | 1 dep | |
| Blog Card Oneblog-card-one | shadcn-extras | Components | Free | no deps | |
| Blog Card Threeblog-card-three | shadcn-extras | Components | Free | 1 dep | |
| Blog Card Twoblog-card-two | shadcn-extras | Components | Free | 1 dep | |
| Bulb Iconbulb-icon | shadcn-extras | Components | Free | 2 deps | |
| Chevron Stepschevron-steps | shadcn-extras | Components | Free | no deps | |
| Circular Gallerycircular-gallery | shadcn-extras | Components | Free | no deps · 3 files |
