Loaders
OrbitLoader
Three moons circle a glowing core on tilted rings, each at its own pace.
Download
- React
- Tailwind CSS v4
- motion
components/ui/orbit-loader.tsx
"use client";
import { motion, useReducedMotion } from "motion/react";
type OrbitLoaderProps = {
className?: string;
color?: 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;
};
/* OrbitLoader: three moons circle a glowing core on tilted rings, each at its own pace. */
export function OrbitLoader({
className = "",
color = "#a78bfa",
size = 72,
speed = 1,
label = "Loading",
}: OrbitLoaderProps) {
const reduce = useReducedMotion();
const rings = [
{ tilt: 0, scale: 1, duration: 1.6 },
{ tilt: 60, scale: 0.8, duration: 2.2 },
{ tilt: -60, scale: 0.6, duration: 1.2 },
];
const moon = Math.max(4, size / 12);
return (
<div role="status" className={`relative inline-block ${className}`} style={{ width: size, height: size }}>
<span className="sr-only">{label}</span>
<motion.span
aria-hidden
className="absolute inset-0 m-auto rounded-full"
style={{ width: size / 5, height: size / 5, background: color, boxShadow: `0 0 ${size / 4}px ${color}` }}
animate={reduce ? undefined : { scale: [1, 1.2, 1] }}
transition={{ duration: 1.4 / speed, repeat: Infinity, ease: "easeInOut" }}
/>
{rings.map((r) => {
const rx = (size / 2) * r.scale;
const ry = rx * 0.4;
const steps = Array.from({ length: 25 }, (_, i) => (i / 24) * Math.PI * 2);
return (
<span key={r.tilt} aria-hidden className="absolute inset-0" style={{ transform: `rotate(${r.tilt}deg)` }}>
<span
className="absolute inset-0 m-auto rounded-[50%] border"
style={{ width: rx * 2, height: ry * 2, borderColor: `color-mix(in srgb, ${color} 25%, transparent)` }}
/>
<motion.span
className="absolute left-1/2 top-1/2 rounded-full"
style={{ width: moon, height: moon, marginLeft: -moon / 2, marginTop: -moon / 2, background: color }}
animate={{ x: steps.map((a) => Math.cos(a) * rx), y: steps.map((a) => Math.sin(a) * ry) }}
transition={{ duration: (reduce ? r.duration * 4 : r.duration) / speed, repeat: Infinity, ease: "linear" }}
/>
</span>
);
})}
</div>
);
}
1. Install the dependency.
npm i motion2. Paste the Code tab into
components/ui/orbit-loader.tsx. The styles are Tailwind CSS v4 classes.3. Use it. This is the preview above:
app/orbit-loader-demo.tsx
import { OrbitLoader } from "@/components/ui/orbit-loader"; export default function OrbitLoaderExample() { return ( <div className="grid h-full place-items-center bg-[radial-gradient(circle_at_50%_50%,#1d1a33,#0d0c14_70%)]"> <OrbitLoader size={88} /> </div> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| color | string | "#a78bfa" | Main 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. |