About
An animated blob background where soft gradient shapes morph and drift like a lava lamp, with optional film grain. Drop it behind hero sections, landing pages and auth screens.
Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Morphing Shape Background to your project and fits it to your theme.
Code
How Morphing Shape Background is used: the demo's code. The component's own source comes with a free account.
Usage.tsx
"use client";
import * as React from "react";
import { MorphingShapeBackground, type MorphingShapeBlob } from "@/components/ui/morphing-shape-background";
const PREVIEW_MATTE_NOISE = `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.65 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>")`;
/* ───────────────────────── Morphing Shape Background, playground ──────────
* Lava-lamp blobs are synthesised on the fly from a palette + count + scale
* + speed, so users can sweep across totally different moods (chili → violet
* → emerald) without every prop needing its own slider.
* ────────────────────────────────────────────────────────────────────────── */
type PaletteId =
| "chili"
| "violet"
| "sky"
| "emerald"
| "amber"
| "mono";
interface MorphingState {
palette: PaletteId;
blobCount: number;
scale: number; // 0..2 multiplier on blob size
speedFactor: number; // 0.4..2 inverse multiplier on duration
blur: number; // 30..140
grain: boolean;
}
const settings = {
palette: "chili",
blobCount: 4,
scale: 1,
speedFactor: 1,
blur: 80,
grain: true,
};
/* Palette gradients, each entry is paired (start, end) so blobs read as
* "atmospheric depth" rather than flat slabs. Ordered loosely warm → cool. */
const PALETTES: Record<
PaletteId,
{ name: string; gradients: string[]; swatch: string }
> = {
chili: {
name: "Wensity Red",
swatch: "linear-gradient(135deg, #cd1c18, #ff8a73)",
gradients: [
"linear-gradient(135deg, #cd1c18 0%, #ff8a73 100%)",
"linear-gradient(135deg, #9b1313 0%, #38000a 100%)",
"linear-gradient(135deg, #ff8a73 0%, #ffe2db 100%)",
"linear-gradient(135deg, #ffa896 0%, #cd1c18 100%)",
],
},
violet: {
name: "Violet",
swatch: "linear-gradient(135deg, #4c1d95, #a78bfa)",
gradients: [
"linear-gradient(135deg, #6d28d9 0%, #c4b5fd 100%)",
"linear-gradient(135deg, #4c1d95 0%, #1e1b4b 100%)",
"linear-gradient(135deg, #a78bfa 0%, #ede9fe 100%)",
"linear-gradient(135deg, #7c3aed 0%, #4c1d95 100%)",
],
},
sky: {
name: "Sky",
swatch: "linear-gradient(135deg, #0369a1, #7dd3fc)",
gradients: [
"linear-gradient(135deg, #0284c7 0%, #7dd3fc 100%)",
"linear-gradient(135deg, #0c4a6e 0%, #082f49 100%)",
"linear-gradient(135deg, #38bdf8 0%, #e0f2fe 100%)",
"linear-gradient(135deg, #0ea5e9 0%, #0369a1 100%)",
],
},
emerald: {
name: "Emerald",
swatch: "linear-gradient(135deg, #047857, #6ee7b7)",
gradients: [
"linear-gradient(135deg, #059669 0%, #6ee7b7 100%)",
"linear-gradient(135deg, #064e3b 0%, #022c22 100%)",
"linear-gradient(135deg, #34d399 0%, #d1fae5 100%)",
"linear-gradient(135deg, #10b981 0%, #047857 100%)",
],
},
amber: {
name: "Amber",
swatch: "linear-gradient(135deg, #b45309, #fcd34d)",
gradients: [
"linear-gradient(135deg, #d97706 0%, #fcd34d 100%)",
"linear-gradient(135deg, #92400e 0%, #451a03 100%)",
"linear-gradient(135deg, #f59e0b 0%, #fef3c7 100%)",
"linear-gradient(135deg, #fbbf24 0%, #b45309 100%)",
],
},
mono: {
name: "Mono",
swatch: "linear-gradient(135deg, #1f2937, #6b7280)",
gradients: [
"linear-gradient(135deg, #374151 0%, #9ca3af 100%)",
"linear-gradient(135deg, #111827 0%, #030712 100%)",
"linear-gradient(135deg, #6b7280 0%, #f3f4f6 100%)",
"linear-gradient(135deg, #4b5563 0%, #1f2937 100%)",
],
},
};
/* Layout templates, anchor positions, opacity, durations. These never
* change with the palette so the user gets a consistent "rhythm" across
* palette swaps. */
const BLOB_TEMPLATES: ReadonlyArray<
Omit<MorphingShapeBlob, "color"> & { sizeBase: number }
> = [
{ sizeBase: 0.85, top: 10, left: 8, blur: 80, opacity: 0.55, duration: 28 },
{ sizeBase: 0.7, top: 35, left: 55, blur: 90, opacity: 0.6, duration: 36, reverse: true, delay: -8 },
{ sizeBase: 0.55, top: 60, left: 20, blur: 70, opacity: 0.4, duration: 32, delay: -14 },
{ sizeBase: 0.45, top: 5, left: 65, blur: 60, opacity: 0.45, duration: 24, reverse: true, delay: -4 },
{ sizeBase: 0.6, top: 45, left: 78, blur: 75, opacity: 0.42, duration: 30, delay: -10 },
{ sizeBase: 0.5, top: 78, left: 70, blur: 65, opacity: 0.38, duration: 26, reverse: true, delay: -2 },
];
function buildBlobs(state: MorphingState): MorphingShapeBlob[] {
const palette = PALETTES[state.palette];
const count = Math.min(Math.max(state.blobCount, 1), BLOB_TEMPLATES.length);
return BLOB_TEMPLATES.slice(0, count).map((tpl, i) => ({
color: palette.gradients[i % palette.gradients.length],
size: (tpl.sizeBase ?? 0.7) * state.scale,
top: tpl.top,
left: tpl.left,
blur: state.blur,
opacity: tpl.opacity,
duration: (tpl.duration ?? 30) / state.speedFactor,
delay: tpl.delay,
reverse: tpl.reverse,
}));
}
/* ───────────────────────── Preview ───────────────────────── */
export function MorphingShapeBackgroundPreview(props: Partial<typeof settings> = {}) {
const state = { ...settings, ...props };
const blobs = React.useMemo(() => buildBlobs(state), [state]);
return (
<div className="relative isolate w-full max-w-6xl overflow-hidden rounded-[1.35rem] border border-black/[0.08] bg-[radial-gradient(ellipse_90%_70%_at_50%_-10%,#ffffff_0%,#f4f4f5_72%)] shadow-[0_1px_2px_rgba(0,0,0,.06),0_18px_46px_-28px_rgba(0,0,0,.26)] dark:border-white/[0.06] dark:bg-[radial-gradient(ellipse_90%_70%_at_50%_-10%,#15151b_0%,#0a0a0b_72%)] dark:shadow-[0_1px_2px_rgba(0,0,0,.4),0_18px_46px_-28px_rgba(0,0,0,.72)]">
<div
aria-hidden
className="pointer-events-none absolute inset-0 opacity-[0.04] mix-blend-overlay dark:opacity-[0.055]"
style={{ backgroundImage: PREVIEW_MATTE_NOISE, backgroundSize: "240px 240px" }}
/>
<div
aria-hidden
className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_bottom,rgba(0,0,0,0.035)_0%,transparent_28%,transparent_74%,rgba(0,0,0,0.06)_100%)] dark:bg-[linear-gradient(to_bottom,rgba(0,0,0,0.18)_0%,transparent_26%,transparent_74%,rgba(0,0,0,0.32)_100%)]"
/>
<MorphingShapeBackground
blobs={blobs}
grain={state.grain}
className="relative h-[600px]"
>
<div className="flex h-[600px] flex-col items-center justify-center gap-4 px-8 text-center">
<h2 className="font-display text-5xl font-extrabold leading-[1.02] tracking-tight text-white sm:text-6xl">
Wensity<span className="ml-1 inline-block h-2.5 w-2.5 self-end bg-red-600 align-baseline" />
</h2>
<p className="max-w-md text-[14px] leading-relaxed text-white/75">
Blobs morph their border-radius and rotate slowly. Grain on top kills banding and adds matte texture.
</p>
</div>
</MorphingShapeBackground>
</div>
);
}
/* ───────────────────────── Controls ───────────────────────── */
/* ───────────────────────── Hero variant ───────────────────────── */
export default function Demo(props: Partial<typeof settings>) {
return <MorphingShapeBackgroundPreview {...props} />;
}
















