Backgrounds
LiquidCanvas
Colour blobs drift and melt into each other like liquid, using an SVG goo filter.
Download
- React
- Tailwind CSS v4
- motion
components/ui/liquid-canvas.tsx
"use client";
import { useId, type ReactNode } from "react";
import { motion, useReducedMotion } from "motion/react";
type LiquidCanvasProps = {
children?: ReactNode;
className?: string;
/** Blobs alternate between these two colours. */
colors?: [string, string];
background?: string;
/** 1 is the default pace; 2 is twice as fast. */
speed?: number;
};
// Position (%), size (px), path (px) and seconds per loop of each blob.
const BLOBS = [
{ left: 15, top: 25, size: 130, x: [0, 90, 30, 0], y: [0, 40, -30, 0], d: 13 },
{ left: 55, top: 15, size: 110, x: [0, -70, 20, 0], y: [0, 60, 90, 0], d: 11 },
{ left: 35, top: 55, size: 150, x: [0, 60, -50, 0], y: [0, -50, 10, 0], d: 15 },
{ left: 70, top: 55, size: 100, x: [0, -90, -30, 0], y: [0, -30, 40, 0], d: 12 },
{ left: 5, top: 65, size: 90, x: [0, 70, 120, 0], y: [0, -60, 10, 0], d: 14 },
{ left: 80, top: 20, size: 80, x: [0, -40, -110, 0], y: [0, 70, 20, 0], d: 10 },
];
/* LiquidCanvas: colour blobs drift and melt into each other like liquid, using an SVG goo filter. */
export function LiquidCanvas({
children,
className = "",
colors = ["#a78bfa", "#f472b6"],
background = "#08070f",
speed = 1,
}: LiquidCanvasProps) {
const reduce = useReducedMotion();
const filterId = `goo-${useId().replace(/[^a-zA-Z0-9-]/g, "")}`;
return (
<div className={`relative overflow-hidden ${className}`} style={{ background }}>
<svg aria-hidden className="absolute h-0 w-0">
<filter id={filterId}>
<feGaussianBlur in="SourceGraphic" stdDeviation="16" result="blur" />
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 24 -10" />
</filter>
</svg>
<div aria-hidden className="absolute inset-0 opacity-90" style={{ filter: `url(#${filterId})` }}>
{BLOBS.map((b, i) => (
<motion.div
key={i}
className="absolute rounded-full"
style={{ left: `${b.left}%`, top: `${b.top}%`, width: b.size, height: b.size, background: colors[i % 2] }}
animate={reduce ? undefined : { x: b.x, y: b.y }}
transition={{ duration: b.d / speed, repeat: Infinity, ease: "easeInOut" }}
/>
))}
</div>
<div className="relative h-full">{children}</div>
</div>
);
}
1. Install the dependency.
npm i motion2. Paste the Code tab into
components/ui/liquid-canvas.tsx. The styles are Tailwind CSS v4 classes.3. Use it. This is the preview above:
app/liquid-canvas-demo.tsx
import { LiquidCanvas } from "@/components/ui/liquid-canvas"; export default function LiquidCanvasExample() { return ( <LiquidCanvas className="h-full"> <p className="absolute bottom-5 left-6 text-2xl font-medium text-white/90">Melting point</p> </LiquidCanvas> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| children | ReactNode | — | Content shown over the background. |
| colors | [string, string] | ["#a78bfa", "#f472b6"] | Blobs alternate between these two colours. |
| background | string | "#08070f" | Base colour. |
| speed | number | 1 | 1 is the default pace; 2 is twice as fast. |
| className | string | "" | Give it a height here. |