Loaders
LiquidLoop
Drops chase each other round a circle and melt together where they meet, using an SVG goo filter.
Download
- React
- Tailwind CSS v4
- motion
components/ui/liquid-loop.tsx
"use client";
import { useId } from "react";
import { motion, useReducedMotion } from "motion/react";
type LiquidLoopProps = {
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;
};
/* LiquidLoop: drops chase each other round a circle and melt together where they meet (SVG goo filter). */
export function LiquidLoop({
className = "",
color = "#a78bfa",
size = 72,
speed = 1,
label = "Loading",
}: LiquidLoopProps) {
const reduce = useReducedMotion();
const filterId = `goo-${useId().replace(/:/g, "")}`;
const drops = [0.34, 0.26, 0.2, 0.15];
return (
<div role="status" className={`relative inline-block ${className}`} style={{ width: size, height: size }}>
<span className="sr-only">{label}</span>
<svg aria-hidden className="absolute h-0 w-0">
<filter id={filterId}>
<feGaussianBlur in="SourceGraphic" stdDeviation={size / 14} result="blur" />
<feColorMatrix in="blur" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -8" />
</filter>
</svg>
<div aria-hidden className="absolute inset-0" style={{ filter: `url(#${filterId})` }}>
{drops.map((d, i) => (
<motion.span
key={d}
className="absolute inset-0"
animate={{ rotate: 360 }}
transition={{
duration: (reduce ? 6 : 1.5) / speed,
repeat: Infinity,
ease: [0.6, 0.1, 0.4, 0.9],
delay: (i * 0.09) / speed,
}}
>
<span
className="absolute left-1/2 rounded-full"
style={{ width: size * d, height: size * d, top: size * 0.08, marginLeft: (-size * d) / 2, background: color }}
/>
</motion.span>
))}
</div>
</div>
);
}
1. Install the dependency.
npm i motion2. Paste the Code tab into
components/ui/liquid-loop.tsx. The styles are Tailwind CSS v4 classes.3. Use it. This is the preview above:
app/liquid-loop-demo.tsx
import { LiquidLoop } from "@/components/ui/liquid-loop"; export default function LiquidLoopExample() { return ( <div className="grid h-full place-items-center bg-[radial-gradient(circle_at_50%_50%,#1d1a33,#0d0c14_70%)]"> <LiquidLoop 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. |