Components
A feature rich loader component. Various props such as full page which is handy for a loading.tsx, easing, color, size. Accessible and performant.
Loading preview...
"use client"
import { useState, useEffect } from "react"
import { Heartbeat } from "@/components/ui/heartbeat-loader"
const COLORS = ["#ff4d4f", "#3b82f6", "#10b981", "#facc15", "#8b5cf6", "#ec4899"]
export default function Page() {
const [rainbow, setRainbow] = useState(false)
const [color, setColor] = useState("#ff4d4f")
const [duration, setDuration] = useState(1.4)
// Rainbow effect: cycle colors automatically
useEffect(() => {
if (!rainbow) return
const interval = setInterval(() => {
const next = COLORS[Math.floor(Math.random() * COLORS.length)]
setColor(next)
}, 500)
return () => clearInterval(interval)
}, [rainbow])
return (
<div className="min-h-screen bg-zinc-950 text-zinc-50">
<div className="max-w-7xl mx-auto px-4 py-16 sm:px-6 lg:px-8">
{/* Header */}
<div className="text-center mb-16">
<h1 className="text-5xl font-bold mb-4 bg-gradient-to-r from-red-400 via-pink-400 to-purple-400 bg-clip-text text-transparent">
Heartbeat Loader
</h1>
<p className="text-zinc-400 text-lg">A beautiful, customizable loading animation</p>
</div>
{/* Interactive Controls */}
<div className="mb-16 bg-zinc-900 rounded-2xl p-8 border border-zinc-800 shadow-2xl">
<h2 className="text-2xl font-semibold mb-6 text-zinc-50">Interactive Demo</h2>
<div className="grid md:grid-cols-2 gap-8">
{/* Controls */}
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-zinc-300 mb-3">
Animation Duration: <span className="text-red-400 font-mono">{duration.toFixed(1)}s</span>
</label>
<input
type="range"
min={0.5}
max={5}
step={0.1}
value={duration}
onChange={(e) => setDuration(Number.parseFloat(e.target.value))}
className="w-full h-2 bg-zinc-800 rounded-lg appearance-none cursor-pointer accent-red-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-zinc-300 mb-3">
Front Color: <span className="text-red-400 font-mono">{color}</span>
</label>
<div className="flex gap-3 items-center">
<input
type="color"
value={color}
onChange={(e) => setColor(e.target.value)}
disabled={rainbow}
className="h-12 w-20 rounded-lg cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed border-2 border-zinc-700"
/>
<div className="flex-1 h-12 rounded-lg border-2 border-zinc-700" style={{ backgroundColor: color }} />
</div>
</div>
<div className="flex items-center gap-3 p-4 bg-zinc-800/50 rounded-lg">
<input
type="checkbox"
id="rainbow"
checked={rainbow}
onChange={(e) => setRainbow(e.target.checked)}
className="w-5 h-5 rounded accent-purple-500 cursor-pointer"
/>
<label htmlFor="rainbow" className="text-sm font-medium text-zinc-300 cursor-pointer select-none">
Rainbow Mode
</label>
</div>
</div>
{/* Live Preview */}
<div className="bg-black/20 rounded-xl border border-zinc-800 flex items-center justify-center min-h-[280px]">
<Heartbeat frontStrokeColor={color} animationDuration={duration} width={80} height={60} strokeWidth={4} />
</div>
</div>
</div>
{/* Variants Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-16">
{/* Default */}
<div className="bg-zinc-900 rounded-xl p-6 border border-zinc-800 hover:border-red-500/50 transition-colors">
<h3 className="text-lg font-semibold mb-4 text-zinc-50">Default</h3>
<div className="bg-black/20 rounded-lg h-32 flex items-center justify-center">
<Heartbeat />
</div>
<code className="text-xs text-zinc-500 mt-3 block">{"<Heartbeat />"}</code>
</div>
{/* Blue */}
<div className="bg-zinc-900 rounded-xl p-6 border border-zinc-800 hover:border-blue-500/50 transition-colors">
<h3 className="text-lg font-semibold mb-4 text-zinc-50">Blue Variant</h3>
<div className="bg-black/20 rounded-lg h-32 flex items-center justify-center">
<Heartbeat frontStrokeColor="#3b82f6" backStrokeColor="rgba(59, 130, 246, 0.2)" />
</div>
<code className="text-xs text-zinc-500 mt-3 block">frontStrokeColor="#3b82f6"</code>
</div>
{/* Green */}
<div className="bg-zinc-900 rounded-xl p-6 border border-zinc-800 hover:border-green-500/50 transition-colors">
<h3 className="text-lg font-semibold mb-4 text-zinc-50">Green Variant</h3>
<div className="bg-black/20 rounded-lg h-32 flex items-center justify-center">
<Heartbeat frontStrokeColor="#10b981" backStrokeColor="rgba(16, 185, 129, 0.2)" />
</div>
<code className="text-xs text-zinc-500 mt-3 block">frontStrokeColor="#10b981"</code>
</div>
{/* Small */}
<div className="bg-zinc-900 rounded-xl p-6 border border-zinc-800 hover:border-purple-500/50 transition-colors">
<h3 className="text-lg font-semibold mb-4 text-zinc-50">Small Size</h3>
<div className="bg-black/20 rounded-lg h-32 flex items-center justify-center">
<Heartbeat width={40} height={30} strokeWidth={2} />
</div>
<code className="text-xs text-zinc-500 mt-3 block">
width={"{40}"} height={"{30}"}
</code>
</div>
{/* Large */}
<div className="bg-zinc-900 rounded-xl p-6 border border-zinc-800 hover:border-pink-500/50 transition-colors">
<h3 className="text-lg font-semibold mb-4 text-zinc-50">Large Size</h3>
<div className="bg-black/20 rounded-lg h-32 flex items-center justify-center">
<Heartbeat width={96} height={72} strokeWidth={4} />
</div>
<code className="text-xs text-zinc-500 mt-3 block">
width={"{96}"} height={"{72}"}
</code>
</div>
{/* Fast */}
<div className="bg-zinc-900 rounded-xl p-6 border border-zinc-800 hover:border-yellow-500/50 transition-colors">
<h3 className="text-lg font-semibold mb-4 text-zinc-50">Fast Animation</h3>
<div className="bg-black/20 rounded-lg h-32 flex items-center justify-center">
<Heartbeat animationDuration={0.7} frontStrokeColor="#facc15" backStrokeColor="rgba(250, 204, 21, 0.2)" />
</div>
<code className="text-xs text-zinc-500 mt-3 block">animationDuration={"{0.7}"}</code>
</div>
</div>
{/* Props Table */}
<div className="bg-zinc-900 rounded-2xl p-8 border border-zinc-800 shadow-2xl">
<h2 className="text-2xl font-semibold mb-6 text-zinc-50">Available Props</h2>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-zinc-800">
<th className="text-left py-3 px-4 text-zinc-300 font-semibold">Prop</th>
<th className="text-left py-3 px-4 text-zinc-300 font-semibold">Type</th>
<th className="text-left py-3 px-4 text-zinc-300 font-semibold">Default</th>
</tr>
</thead>
<tbody className="text-zinc-400">
<tr className="border-b border-zinc-800/50 hover:bg-zinc-800/30">
<td className="py-3 px-4 font-mono text-red-400">width</td>
<td className="py-3 px-4">number</td>
<td className="py-3 px-4 font-mono">64</td>
</tr>
<tr className="border-b border-zinc-800/50 hover:bg-zinc-800/30">
<td className="py-3 px-4 font-mono text-red-400">height</td>
<td className="py-3 px-4">number</td>
<td className="py-3 px-4 font-mono">48</td>
</tr>
<tr className="border-b border-zinc-800/50 hover:bg-zinc-800/30">
<td className="py-3 px-4 font-mono text-red-400">strokeWidth</td>
<td className="py-3 px-4">number</td>
<td className="py-3 px-4 font-mono">3</td>
</tr>
<tr className="border-b border-zinc-800/50 hover:bg-zinc-800/30">
<td className="py-3 px-4 font-mono text-red-400">frontStrokeColor</td>
<td className="py-3 px-4">string</td>
<td className="py-3 px-4 font-mono">#ff4d4f</td>
</tr>
<tr className="border-b border-zinc-800/50 hover:bg-zinc-800/30">
<td className="py-3 px-4 font-mono text-red-400">animationDuration</td>
<td className="py-3 px-4">number</td>
<td className="py-3 px-4 font-mono">1.4</td>
</tr>
<tr className="border-b border-zinc-800/50 hover:bg-zinc-800/30">
<td className="py-3 px-4 font-mono text-red-400">centered</td>
<td className="py-3 px-4">boolean</td>
<td className="py-3 px-4 font-mono">false</td>
</tr>
<tr className="hover:bg-zinc-800/30">
<td className="py-3 px-4 font-mono text-red-400">fullscreen</td>
<td className="py-3 px-4">boolean</td>
<td className="py-3 px-4 font-mono">false</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
)
}