# Gradient Mesh Blobs — Animated React Background

> Five saturated blobs wander on separate looping paths and melt into a mesh gradient, while one extra blob follows the cursor on a spring.

- Category: [Backgrounds](https://pixelpapa.com/components/backgrounds.md)
- Tags: background, gradient, mesh, blobs, cursor
- Dependencies: motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/backgrounds/bg-gradient-mesh-blobs

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/bg-gradient-mesh-blobs.json"
```

The file is written to `components/pixelpapa/bg-gradient-mesh-blobs.tsx`. To install manually, run `npm install motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { motion, useMotionValue, useReducedMotion, useSpring } from "motion/react";
import { useRef, type PointerEvent, type ReactNode } from "react";
import { cn } from "@/lib/utils";

type GradientMeshBlobsProps = {
  children?: ReactNode;
  className?: string;
  /** Five CSS colours for the wandering blobs. */
  colors?: [string, string, string, string, string];
  /** Colour of the blob that follows the cursor. */
  pointerColor?: string;
  /** Seconds for the slowest blob to finish its path. */
  speed?: number;
  /** Blur radius in px. Larger is smoother and softer. */
  blur?: number;
};

// Start position and size (in % of the container) plus the path each blob walks (in % of its own size).
const blobs = [
  { left: "-8%", top: "-18%", size: "52%", x: [0, 55, 20, 0], y: [0, 25, 60, 0], scale: [1, 1.2, 0.9, 1], t: 1 },
  { left: "58%", top: "-22%", size: "48%", x: [0, -45, -10, 0], y: [0, 40, 15, 0], scale: [1, 0.85, 1.15, 1], t: 0.8 },
  { left: "30%", top: "28%", size: "44%", x: [0, 35, -40, 0], y: [0, -30, 20, 0], scale: [1, 1.25, 1, 1], t: 0.65 },
  { left: "-12%", top: "52%", size: "50%", x: [0, 40, 70, 0], y: [0, -35, 5, 0], scale: [1, 1.1, 0.85, 1], t: 0.9 },
  { left: "62%", top: "48%", size: "54%", x: [0, -50, -20, 0], y: [0, -20, -55, 0], scale: [1, 0.9, 1.2, 1], t: 0.75 },
];

export function GradientMeshBlobs({
  children,
  className,
  colors = ["var(--chart-1)", "var(--chart-2)", "var(--chart-3)", "var(--chart-4)", "var(--chart-5)"],
  pointerColor = "var(--chart-3)",
  speed = 22,
  blur = 90,
}: GradientMeshBlobsProps) {
  const ref = useRef<HTMLDivElement>(null);
  const reduce = useReducedMotion();
  const px = useMotionValue(0);
  const py = useMotionValue(0);
  const sx = useSpring(px, { stiffness: 40, damping: 16, mass: 1.2 });
  const sy = useSpring(py, { stiffness: 40, damping: 16, mass: 1.2 });

  function onPointerMove(e: PointerEvent<HTMLDivElement>) {
    const r = ref.current?.getBoundingClientRect();
    if (!r) return;
    px.set(e.clientX - r.left - r.width / 2);
    py.set(e.clientY - r.top - r.height / 2);
  }

  return (
    <div
      ref={ref}
      onPointerMove={onPointerMove}
      className={cn("relative isolate overflow-hidden bg-background", className)}
    >
      {/* Each blob is blurred once and then only moved by transform, so the compositor does all the work. */}
      <div
        aria-hidden
        className="pointer-events-none absolute -inset-[10%] -z-10 opacity-70 saturate-150 dark:opacity-55"
      >
        {blobs.map((b, i) => (
          <motion.div
            key={i}
            className="absolute aspect-square rounded-full will-change-transform"
            style={{ left: b.left, top: b.top, width: b.size, background: colors[i], filter: `blur(${blur}px)` }}
            animate={
              reduce
                ? undefined
                : { x: b.x.map((v) => `${v}%`), y: b.y.map((v) => `${v}%`), scale: b.scale }
            }
            transition={{ duration: speed * b.t, ease: "easeInOut", repeat: Infinity }}
          />
        ))}
        <motion.div
          className="absolute top-1/2 left-1/2 -mt-[14%] -ml-[14%] aspect-square w-[28%] rounded-full will-change-transform"
          style={{ x: sx, y: sy, background: pointerColor, filter: `blur(${blur}px)` }}
        />
      </div>
      {/* Grain hides banding in the long gradients. */}
      <div
        aria-hidden
        className="pointer-events-none absolute inset-0 -z-10 opacity-[0.18] mix-blend-soft-light"
        style={{
          backgroundImage:
            "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\")",
        }}
      />
      {children}
    </div>
  );
}

export default function GradientMeshBlobsDemo() {
  return (
    <GradientMeshBlobs className="flex min-h-screen w-full items-center justify-center p-6">
      <div className="w-full max-w-lg rounded-3xl border border-white/40 bg-background/55 p-10 text-center shadow-2xl shadow-black/10 backdrop-blur-2xl dark:border-white/10 dark:bg-background/40">
        <p className="text-xs font-medium tracking-[0.2em] text-muted-foreground uppercase">Palette 07 — Tidewater</p>
        <h2 className="mt-4 text-4xl font-semibold tracking-tight text-foreground sm:text-5xl">
          Gradients you can stir
        </h2>
        <p className="mx-auto mt-4 max-w-sm text-muted-foreground">
          Five blobs keep to their own slow orbits. A sixth one chases your cursor and mixes the colours as it
          goes.
        </p>
        <div className="mt-7 flex justify-center gap-2">
          {["bg-chart-1", "bg-chart-2", "bg-chart-3", "bg-chart-4", "bg-chart-5"].map((c) => (
            <span key={c} className={cn("size-7 rounded-full ring-2 ring-background", c)} />
          ))}
        </div>
      </div>
    </GradientMeshBlobs>
  );
}
```

## AI prompt

```text
Add the "Gradient Mesh Blobs" component from PixelPapa UI to this project.

What it is: Five saturated blobs wander on separate looping paths and melt into a mesh gradient, while one extra blob follows the cursor on a spring.
Category: Backgrounds. Depends on: motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/bg-gradient-mesh-blobs.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/bg-gradient-mesh-blobs.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/bg-gradient-mesh-blobs" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More backgrounds

- [Animated Grid Lines](https://pixelpapa.com/components/backgrounds/bg-animated-grid-lines.md) (Pro): A perspective floor of grid lines that scrolls toward the viewer forever, with light pulses racing down a few of the lines.
- [Aurora Background](https://pixelpapa.com/components/backgrounds/aurora-background.md): Slow drifting colour ribbons behind your content, built from blurred gradients with no canvas.
- [Beams Light Rays](https://pixelpapa.com/components/backgrounds/bg-beams-light-rays.md) (Pro): Soft light rays fan out from above and sway a few degrees while breathing in brightness, with dust motes drifting through them.
- [Dot Grid Spotlight](https://pixelpapa.com/components/backgrounds/bg-dot-grid-spotlight.md) (Pro): A quiet dot grid where a soft light trails the cursor and swells the dots it passes over; it wanders on its own when idle.
- [Flow Field](https://pixelpapa.com/components/backgrounds/bg-flow-field.md) (Pro): Hundreds of canvas particles ride a slowly shifting vector field and leave fading silk trails in your theme colours.
- [Hex Tiles Hover](https://pixelpapa.com/components/backgrounds/bg-hex-tiles-hover.md) (Pro): A canvas honeycomb (or square grid) where the tile under the cursor lights up, spills into its neighbours and slowly cools, with stray tiles sparking while idle.
