Loader Collection
A collection of 10 minimal, premium loaders featuring smooth animations for loading states and transitions.
Installation
Add this component to your project using the CLI:
npx vui-registry-cli-v1 add loader-stackSource Code
'use client'
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import SpinnerLoader from './spinner-loader'
import DotsLoader from './dots-loader'
import BarLoader from './bar-loader'
import PulseLoader from './pulse-loader'
import WaveLoader from './wave-loader'
import RingLoader from './ring-loader'
import OrbitLoader from './orbit-loader'
import BounceLoader from './bounce-loader'
import FadeLoader from './fade-loader'
export default function LoaderStack() {
const [copied, setCopied] = useState<string | null>(null)
const copy = (name: string, cmd: string) => {
navigator.clipboard.writeText(cmd)
setCopied(name)
setTimeout(() => setCopied(null), 1200)
}
const variants = [
{ name: 'Spinner', Component: SpinnerLoader, cmd: 'npx vui-registry-cli-v1 add loader-spinner' },
{ name: 'Dots', Component: DotsLoader, cmd: 'npx vui-registry-cli-v1 add loader-dots' },
{ name: 'Bar', Component: BarLoader, cmd: 'npx vui-registry-cli-v1 add loader-bar' },
{ name: 'Pulse', Component: PulseLoader, cmd: 'npx vui-registry-cli-v1 add loader-pulse' },
{ name: 'Wave', Component: WaveLoader, cmd: 'npx vui-registry-cli-v1 add loader-wave' },
{ name: 'Ring', Component: RingLoader, cmd: 'npx vui-registry-cli-v1 add loader-ring' },
{ name: 'Orbit', Component: OrbitLoader, cmd: 'npx vui-registry-cli-v1 add loader-orbit' },
{ name: 'Bounce', Component: BounceLoader, cmd: 'npx vui-registry-cli-v1 add loader-bounce' },
{ name: 'Fade', Component: FadeLoader, cmd: 'npx vui-registry-cli-v1 add loader-fade' },
]
return (
<div className="w-full grid gap-6 md:grid-cols-2 p-4">
{variants.map((v) => (
<div key={v.name} className="rounded-2xl p-[2px] border border-border bg-card/50 backdrop-blur-sm overflow-hidden">
<div className="rounded-xl border border-border bg-background/60">
<div className="flex items-center justify-between px-4 py-3 border-b border-border/50 bg-muted/10 relative">
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wider">{v.name}</div>
<button
onClick={() => copy(v.name, v.cmd)}
className="text-[10px] font-mono border-b border-border absolute top-2 right-3"
>
<AnimatePresence mode="wait">
{copied === v.name ? (
<motion.span key="copied" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className="text-emerald-500">
Copied
</motion.span>
) : (
<motion.span key="copy" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
CLI
</motion.span>
)}
</AnimatePresence>
</button>
</div>
<div className="p-6 min-h-[140px] grid place-items-center">
<v.Component />
</div>
</div>
</div>
))}
</div>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
export default function EliteSpinner() {
return (
<div className="flex flex-col items-center justify-center py-20">
<div className="relative w-12 h-12">
{/* 1. THE TRACK: The background "rail" for the spinner */}
<div className="absolute inset-0 rounded-full border-[3px] border-zinc-100 dark:border-zinc-800/50" />
{/* 2. THE SPINNER: High-contrast, weighted arcs */}
<motion.svg
viewBox="0 0 50 50"
className="absolute inset-0 w-full h-full"
animate={{ rotate: 360 }}
transition={{
duration: 1,
repeat: Infinity,
ease: "linear"
}}
>
{/* Primary Arc (The Head) */}
<motion.circle
cx="25"
cy="25"
r="21"
fill="none"
stroke="currentColor" // Adapts to text color
strokeWidth="3.5"
strokeLinecap="round"
strokeDasharray="30 150" // Precise arc length
className="text-zinc-900 dark:text-zinc-100"
/>
{/* Secondary Arc (The Tail - for visual weight) */}
<motion.circle
cx="25"
cy="25"
r="21"
fill="none"
stroke="currentColor"
strokeWidth="3.5"
strokeLinecap="round"
strokeDasharray="1 150"
className="text-zinc-400 dark:text-zinc-600"
style={{ rotate: 120 }} // Positioned behind the head
/>
</motion.svg>
{/* 3. CENTER GLOW: Subtle light source in the middle */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-1 h-1 rounded-full bg-zinc-400 dark:bg-zinc-500 opacity-20" />
</div>
</div>
{/* 4. REFINED STATUS: Minimalist, spaced typography */}
<motion.p
initial={{ opacity: 0.4 }}
animate={{ opacity: [0.4, 1, 0.4] }}
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
className="mt-6 text-[11px] font-medium tracking-[0.3em] uppercase text-zinc-500 dark:text-zinc-400"
>
Processing
</motion.p>
</div>
)
}Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
export default function DotsLoader() {
return (
<div className="flex items-center gap-2">
{[0, 1, 2].map((i) => (
<motion.div
key={i}
className="w-2.5 h-2.5 rounded-full bg-foreground/70"
animate={{ y: [0, -6, 0], opacity: [0.6, 1, 0.6] }}
transition={{ repeat: Infinity, duration: 0.8, delay: i * 0.1 }}
/>
))}
</div>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
export default function LuxuryBeamLoader() {
return (
<div className="flex flex-col items-start gap-3 py-10 px-6">
<div className="relative w-72 group">
{/* 1. THE RAIL: Ultra-thin, low-contrast foundation */}
<div className="h-[1px] w-full bg-zinc-200 dark:bg-zinc-800 rounded-full" />
{/* 2. THE EMITTER: A localized glow that moves with the bar */}
<motion.div
initial={{ x: "0%" }}
animate={{ x: "100%" }}
transition={{
repeat: Infinity,
duration: 2,
ease: [0.4, 0, 0.2, 1],
}}
className="absolute top-0 left-0 w-24 h-[1px] overflow-visible"
>
{/* Central Bright Beam */}
<div className="w-full h-full bg-gradient-to-r from-transparent via-zinc-950 dark:via-white to-transparent" />
{/* Light Leak / Bloom effect */}
<div className="absolute inset-0 w-full h-full bg-zinc-950 dark:bg-white blur-[4px] opacity-30" />
</motion.div>
{/* 3. THE "CHASE" STREAK: A secondary, faster pulse for complexity */}
<motion.div
initial={{ x: "-20%" }}
animate={{ x: "120%" }}
transition={{
repeat: Infinity,
duration: 1.5,
ease: "easeInOut",
delay: 0.1,
}}
className="absolute top-0 left-0 w-16 h-[1px]"
>
<div className="w-full h-full bg-gradient-to-r from-transparent via-zinc-400 dark:via-zinc-500 to-transparent opacity-40" />
</motion.div>
</div>
{/* 4. TECHNICAL DATA: Minimalist layout */}
<div className="w-72 flex justify-between items-baseline">
<div className="flex flex-col">
<span className="text-[9px] font-bold uppercase tracking-[0.3em] text-zinc-900 dark:text-zinc-100">
System Hash
</span>
<span className="text-[8px] font-mono text-zinc-400 dark:text-zinc-600">
0x71C...A42
</span>
</div>
<div className="text-right">
<motion.span
animate={{ opacity: [0.4, 1, 0.4] }}
transition={{ duration: 2, repeat: Infinity }}
className="text-[10px] font-medium text-zinc-500"
>
Processing...
</motion.span>
</div>
</div>
</div>
)
}Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
export default function PulseLoader() {
return (
<motion.div
className="w-6 h-6 rounded-full bg-foreground/60"
animate={{ scale: [1, 1.3, 1], opacity: [0.8, 1, 0.8] }}
transition={{ repeat: Infinity, duration: 0.9, ease: 'easeInOut' }}
/>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
export default function WaveLoader() {
return (
<div className="flex items-end gap-1 h-6">
{[0, 1, 2, 3, 4].map((i) => (
<motion.div
key={i}
className="w-2 bg-foreground/70 rounded"
animate={{ height: [8, 24, 8] }}
transition={{ repeat: Infinity, duration: 1.2, delay: i * 0.08 }}
/>
))}
</div>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
export default function RingLoader() {
return (
<div className="relative w-12 h-12">
<motion.div
className="absolute inset-0 rounded-full border-2 border-foreground/20"
/>
<motion.div
className="absolute inset-0 rounded-full border-2 border-transparent border-t-foreground"
animate={{ rotate: 360 }}
transition={{ repeat: Infinity, duration: 1.2, ease: 'linear' }}
/>
</div>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
export default function OrbitLoader() {
return (
<div className="relative w-12 h-12">
<motion.div
className="absolute inset-0 rounded-full border border-foreground/20"
/>
<motion.div
className="absolute left-1/2 top-0 w-1.5 h-1.5 -ml-0.75 rounded-full bg-foreground"
animate={{ rotate: 360 }}
style={{ transformOrigin: '0 12px' }}
transition={{ repeat: Infinity, duration: 1.4, ease: 'linear' }}
/>
</div>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
export default function VelocityShardLoader() {
return (
<div className="flex flex-col items-center justify-center py-20 gap-10">
<div className="relative w-20 h-20 flex items-center justify-center">
{/* 1. THE VELOCITY RING: Gauge backdrops */}
<div className="absolute inset-0 rounded-full border-[1px] border-zinc-200 dark:border-zinc-800" />
<div className="absolute inset-2 rounded-full border-[0.5px] border-zinc-100 dark:border-zinc-900" />
{/* 2. THE PRECISION SHARDS: High-speed staggering */}
{[...Array(12)].map((_, i) => (
<motion.div
key={i}
className="absolute inset-0 flex items-start justify-center pt-1"
style={{ rotate: `${i * 30}deg` }}
>
<motion.div
className="w-[2.5px] rounded-full bg-zinc-950 dark:bg-white"
animate={{
height: [4, 16, 4],
opacity: [0.1, 1, 0.1],
y: [0, 2, 0]
}}
transition={{
repeat: Infinity,
duration: 1,
delay: i * 0.08,
ease: [0.4, 0, 0.2, 1]
}}
/>
</motion.div>
))}
{/* 3. THE CLEAN CORE: Minimalist central hub */}
<div className="relative w-6 h-6 border-[0.5px] border-zinc-200 dark:border-zinc-800 rounded-full flex items-center justify-center bg-transparent">
<motion.div
animate={{ scale: [1, 1.2, 1], opacity: [0.5, 1, 0.5] }}
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
className="w-1.5 h-1.5 bg-zinc-950 dark:bg-white rounded-full shadow-[0_0_8px_rgba(255,255,255,0.3)]"
/>
</div>
</div>
{/* 4. BRANDING: Velocity UI Layout */}
<div className="flex flex-col items-center gap-1.5">
<div className="flex items-center gap-3">
<span className="text-[11px] font-black uppercase tracking-[0.4em] text-zinc-950 dark:text-white">
Velocity<span className="text-zinc-400 font-light ml-1">UI</span>
</span>
</div>
{/* Dynamic Status Bar */}
<div className="flex items-center gap-2">
<div className="h-[2px] w-12 bg-zinc-100 dark:bg-zinc-900 overflow-hidden relative rounded-full">
<motion.div
animate={{ x: ["-100%", "100%"] }}
transition={{ duration: 1.2, repeat: Infinity, ease: "easeInOut" }}
className="absolute inset-0 bg-zinc-950 dark:bg-white w-1/2 rounded-full"
/>
</div>
<span className="text-[8px] font-mono text-zinc-500 uppercase tracking-tighter">
System.Active
</span>
</div>
</div>
</div>
)
}Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
export default function FadeLoader() {
const bars = [0, 1, 2, 3]
return (
<div className="flex items-center gap-1.5" aria-label="Loading content">
{bars.map((i) => (
<motion.div
key={i}
className="w-1.5 h-5 rounded-full bg-current opacity-20"
animate={{
opacity: [0.2, 1, 0.2],
scaleY: [1, 1.3, 1], // Adds a "pulse" feel
}}
transition={{
repeat: Infinity,
duration: 1,
delay: i * 0.15, // Better staggered rhythm
ease: "easeInOut"
}}
/>
))}
</div>
)
}Dependencies
framer-motion: latestclsx: latesttailwind-merge: latest
Props
Component property reference.
| Name | Type | Default | Description |
|---|---|---|---|
| size | number | 40 | Size of the loader in pixels. |
| color | string | 'currentColor' | CSS color for the loader elements. |
| speed | number | 1 | Animation speed multiplier. |
Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.
I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.
I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.

