Loaders
QuantumRing
Two glowing arcs spin opposite ways, stretching as they turn, while a particle jumps between four spots.
Download
- React
- Tailwind CSS v4
- motion
components/ui/quantum-ring.tsx
"use client";
import { motion, useReducedMotion } from "motion/react";
type QuantumRingProps = {
className?: string;
color?: string;
/** Colour of the inner ring. */
accent?: string;
/** Loader width in pixels. */
size?: number;
/** 1 is the default pace; 2 is twice as fast. */
speed?: number;
/** Read out by screen readers. */
label?: string;
};
/* One arc of a circle that turns and stretches/shrinks as it goes. */
function Arc({ r, color, width, duration, reverse, still }: {
r: number;
color: string;
width: number;
duration: number;
reverse?: boolean;
still: boolean;
}) {
return (
<motion.svg
aria-hidden
viewBox="0 0 100 100"
className="absolute inset-0 h-full w-full"
style={{ filter: `drop-shadow(0 0 3px ${color})` }}
animate={still ? undefined : { rotate: reverse ? -360 : 360 }}
transition={{ duration, repeat: Infinity, ease: "linear" }}
>
<motion.circle
cx="50"
cy="50"
r={r}
fill="none"
stroke={color}
strokeWidth={width}
strokeLinecap="round"
pathLength={100}
initial={{ strokeDasharray: "15 85" }}
animate={still ? { strokeDasharray: "30 70" } : { strokeDasharray: ["15 85", "60 40", "15 85"] }}
transition={{ duration: duration * 1.5, repeat: Infinity, ease: "easeInOut" }}
/>
</motion.svg>
);
}
/* QuantumRing: two arcs spin opposite ways while a particle jumps between four spots between them. */
export function QuantumRing({
className = "",
color = "#a78bfa",
accent = "#22d3ee",
size = 72,
speed = 1,
label = "Loading",
}: QuantumRingProps) {
const reduce = !!useReducedMotion();
const spin = 1.6 / speed;
const jump = 1.2 / speed;
const dot = size * 0.07;
return (
<div role="status" className={`relative inline-block ${className}`} style={{ width: size, height: size }}>
<span className="sr-only">{label}</span>
<Arc r={44} color={color} width={5} duration={spin} still={reduce} />
<Arc r={28} color={accent} width={4} duration={spin * 0.8} reverse still={reduce} />
{/* Four spots on the middle circle; the particle shows at one at a time */}
{[0, 1, 2, 3].map((i) => (
<span
key={i}
aria-hidden
className="absolute inset-0"
style={{ transform: `rotate(${45 + i * 90}deg)` }}
>
<motion.span
className="absolute left-1/2 rounded-full bg-white"
style={{
width: dot,
height: dot,
marginLeft: -dot / 2,
top: size * 0.14 - dot / 2,
boxShadow: `0 0 ${dot * 1.5}px ${color}`,
}}
initial={{ opacity: 0 }}
animate={reduce ? { opacity: [0.2, 0.7, 0.2] } : { opacity: [0, 1, 0, 0], scale: [0.4, 1.2, 0.4, 0.4] }}
transition={
reduce
? { duration: 3, repeat: Infinity, delay: i * 0.75 }
: { duration: jump, times: [0, 0.06, 0.25, 1], repeat: Infinity, ease: "easeOut", delay: (i * jump) / 4 }
}
/>
</span>
))}
</div>
);
}
1. Install the dependency.
npm i motion2. Paste the Code tab into
components/ui/quantum-ring.tsx. The styles are Tailwind CSS v4 classes.3. Use it. This is the preview above:
app/quantum-ring-demo.tsx
import { QuantumRing } from "@/components/ui/quantum-ring"; export default function QuantumRingExample() { return ( <div className="grid h-full place-items-center bg-[radial-gradient(circle_at_50%_50%,#1d1a33,#0d0c14_70%)]"> <QuantumRing size={96} /> </div> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| color | string | "#a78bfa" | Outer ring colour. |
| accent | string | "#22d3ee" | Inner ring colour. |
| size | number | 72 | Width in pixels. |
| speed | number | 1 | 1 is the default pace; 2 is twice as fast. |
| label | string | "Loading" | Read out by screen readers (role=status). |
| className | string | "" | Extra classes on the wrapper. |