ShadersGradient Backgroundsno dependencies

MeshGradient

Five large, heavily blurred color blobs drift on independent CSS keyframe loops over a deep base color, with an optional animated film-grain overlay for a Stripe-style mesh hero.

Preview

Mesh Gradient

Soft color fields drifting on independent loops.

Customize

11 props
1
70
0.18
360
20

Installation

Copies the component source and its CSS Module into your project.

npx motionfoundry add mesh-gradient

Writes components/ui/mesh-gradient.tsx and components/styles/mesh-gradient.module.css.

Usage

Updates live as you change the controls — only props that differ from the defaults are included.

example.tsx
1import { MeshGradient } from "@motionfoundry/ui";2 3export function Example() {4  return (5    <MeshGradient />6  );7}

Props

PropTypeDefault
color1

First blob color.

string'#ff5c9a'
color2

Second blob color.

string'#7c5cff'
color3

Third blob color.

string'#00c2ff'
color4

Fourth blob color.

string'#ffb36b'
backgroundColor

Base surface color behind the blobs.

string'#0c0a1f'
speed

Animation speed multiplier (0 pauses).

number1
blur

Blur radius of the blob layer in px.

number70
grain

Overlay an animated film-grain texture.

booleantrue
grainOpacity

Opacity of the grain overlay (0–1).

number0.18
height

Height of the background in px.

number360
borderRadius

Corner radius in px.

number20
children

Content rendered centered on top; defaults to a heading and subline.

React.ReactNodeundefined
className

Additional class merged onto the root element.

string''
style

Inline styles merged onto the root element.

React.CSSPropertiesundefined

Accessibility

All blob, sheen and grain layers are aria-hidden and pointer-events: none; children stay in normal reading order. Animations pause when the element is off-screen or speed is 0, and prefers-reduced-motion renders a static mesh (honored in both CSS and JS).

Toggle prefers-reduced-motion in your OS or browser devtools (Rendering → Emulate CSS media feature) to verify the reduced-motion state.

Source

components/ui/mesh-gradient.tsx
1"use client";2 3import * as React from "react";4import styles from "../styles/mesh-gradient.module.css";5 6export interface MeshGradientProps {7  /** First blob color */8  color1?: string;9  /** Second blob color */10  color2?: string;11  /** Third blob color */12  color3?: string;13  /** Fourth blob color */14  color4?: string;15  /** Base surface color behind the blobs */16  backgroundColor?: string;17  /** Animation speed multiplier (1 = default drift, 0 = paused) */18  speed?: number;19  /** Blur radius applied to the blob layer, in px */20  blur?: number;21  /** Overlay an animated film grain texture */22  grain?: boolean;23  /** Opacity of the film grain overlay (0–1) */24  grainOpacity?: number;25  /** Height of the background in px */26  height?: number;27  /** Corner radius in px */28  borderRadius?: number;29  /** Foreground content rendered centered on top */30  children?: React.ReactNode;31  /** Optional className merged onto the root */32  className?: string;33  /** Inline styles merged onto the root */34  style?: React.CSSProperties;35}36 37/** Blob layout: position (%), size (% of the larger side), base loop duration (s), keyframe path index */38const BLOBS = [39  { x: 18, y: 22, size: 62, dur: 22, path: 0, color: 0 },40  { x: 78, y: 18, size: 58, dur: 27, path: 1, color: 1 },41  { x: 72, y: 82, size: 66, dur: 31, path: 2, color: 2 },42  { x: 22, y: 80, size: 56, dur: 25, path: 3, color: 3 },43  { x: 50, y: 50, size: 44, dur: 19, path: 4, color: 0 },44];45 46function useReducedMotionPref() {47  const [reduced, setReduced] = React.useState(false);48  React.useEffect(() => {49    if (typeof window === "undefined" || !window.matchMedia) return;50    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");51    const update = () => setReduced(mq.matches);52    update();53    mq.addEventListener("change", update);54    return () => mq.removeEventListener("change", update);55  }, []);56  return reduced;57}58 59function useInView(ref: React.RefObject<HTMLElement | null>) {60  const [inView, setInView] = React.useState(true);61  React.useEffect(() => {62    const el = ref.current;63    if (!el || typeof IntersectionObserver === "undefined") return;64    const io = new IntersectionObserver((entries) => {65      const entry = entries[entries.length - 1];66      if (entry) setInView(entry.isIntersecting);67    }, { rootMargin: "80px" });68    io.observe(el);69    return () => io.disconnect();70  }, [ref]);71  return inView;72}73 74export function MeshGradient({75  color1 = "#ff5c9a",76  color2 = "#7c5cff",77  color3 = "#00c2ff",78  color4 = "#ffb36b",79  backgroundColor = "#0c0a1f",80  speed = 1,81  blur = 70,82  grain = true,83  grainOpacity = 0.18,84  height = 360,85  borderRadius = 20,86  children,87  className = "",88  style,89}: MeshGradientProps) {90  const rootRef = React.useRef<HTMLDivElement>(null);91  const reduced = useReducedMotionPref();92  const inView = useInView(rootRef);93  const colors = [color1, color2, color3, color4];94  const safeSpeed = Math.max(0, speed);95  const running = !reduced && safeSpeed > 0 && inView;96 97  const styleVars = {98    "--mf-mg-bg": backgroundColor,99    "--mf-mg-blur": `${Math.max(0, blur)}px`,100    "--mf-mg-grain": String(Math.min(1, Math.max(0, grainOpacity))),101    "--mf-mg-state": running ? "running" : "paused",102    height: `${height}px`,103    borderRadius: `${borderRadius}px`,104    ...style,105  } as React.CSSProperties;106 107  return (108    <div ref={rootRef} style={styleVars} className={`${styles.root} ${className}`.trim()}>109      <div aria-hidden="true" className={styles.blobs}>110        {BLOBS.map((b, i) => (111          <span112            key={i}113            className={`${styles.blob} ${styles[`path${b.path}`]}`}114            style={{115              left: `${b.x}%`,116              top: `${b.y}%`,117              width: `${b.size}%`,118              background: `radial-gradient(circle at 50% 50%, ${colors[b.color]} 0%, ${colors[b.color]} 30%, transparent 68%)`,119              animationDuration: `${(b.dur / (safeSpeed > 0 ? safeSpeed : 1)).toFixed(2)}s`,120              animationDelay: `-${(b.dur * (i * 0.17)).toFixed(2)}s`,121              opacity: i === 4 ? 0.7 : 0.9,122            }}123          />124        ))}125      </div>126      <div aria-hidden="true" className={styles.sheen} />127      {grain ? <div aria-hidden="true" className={styles.grain} /> : null}128      <div className={styles.content}>129        {children ?? (130          <div className={styles.copy}>131            <h3 className={styles.title}>Mesh Gradient</h3>132            <p className={styles.subtitle}>Soft color fields drifting on independent loops.</p>133          </div>134        )}135      </div>136    </div>137  );138}