ShadersGradient Backgroundsno dependencies

ConicGradient

A rotating conic-gradient core with a wide blurred halo, a crisp counter-rotating inner ring and fine noise; the whole origin leans slightly toward the pointer with a lerped tilt.

Preview

Conic Gradient

A slow-turning spectrum core with a soft halo.

Customize

13 props
1
60
55
360
20

Installation

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

npx motionfoundry add conic-gradient

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

Usage

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

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

Props

PropTypeDefault
color1

First conic stop color.

string'#ff3d7f'
color2

Second conic stop color.

string'#ffb020'
color3

Third conic stop color.

string'#18d6a3'
color4

Fourth conic stop color.

string'#5b6cff'
backgroundColor

Surface color behind the glow.

string'#07070b'
speed

Rotation speed multiplier; 1 = one turn per 14s, 0 pauses.

number1
blur

Blur radius of the outer glow in px.

number60
spread

Diameter of the conic core as a percentage of the component width.

number55
interactive

Tilt the gradient origin slightly toward the pointer.

booleantrue
ring

Show the crisp counter-rotating inner ring.

booleantrue
noise

Overlay a fine static noise texture.

booleantrue
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

Glow, ring and noise layers are aria-hidden with pointer-events: none, so the pointer tilt never blocks interaction with children. Rotation pauses off-screen or at speed 0; prefers-reduced-motion stops rotation and disables the pointer tilt.

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

Source

components/ui/conic-gradient.tsx
1"use client";2 3import * as React from "react";4import styles from "../styles/conic-gradient.module.css";5 6export interface ConicGradientProps {7  /** First conic stop color */8  color1?: string;9  /** Second conic stop color */10  color2?: string;11  /** Third conic stop color */12  color3?: string;13  /** Fourth conic stop color */14  color4?: string;15  /** Surface color behind the glow */16  backgroundColor?: string;17  /** Rotation speed multiplier (1 = one turn per 14s, 0 = paused) */18  speed?: number;19  /** Blur radius of the outer glow, in px */20  blur?: number;21  /** Diameter of the conic core as a percentage of the component width */22  spread?: number;23  /** Tilt the gradient origin slightly toward the pointer */24  interactive?: boolean;25  /** Show the crisp counter-rotating inner ring */26  ring?: boolean;27  /** Overlay a fine static noise texture */28  noise?: boolean;29  /** Height of the background in px */30  height?: number;31  /** Corner radius in px */32  borderRadius?: number;33  /** Foreground content rendered centered on top */34  children?: React.ReactNode;35  /** Optional className merged onto the root */36  className?: string;37  /** Inline styles merged onto the root */38  style?: React.CSSProperties;39}40 41const BASE_TURN_S = 14;42const MAX_TILT_PX = 36;43 44function useReducedMotionPref() {45  const [reduced, setReduced] = React.useState(false);46  React.useEffect(() => {47    if (typeof window === "undefined" || !window.matchMedia) return;48    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");49    const update = () => setReduced(mq.matches);50    update();51    mq.addEventListener("change", update);52    return () => mq.removeEventListener("change", update);53  }, []);54  return reduced;55}56 57function useInView(ref: React.RefObject<HTMLElement | null>) {58  const [inView, setInView] = React.useState(true);59  React.useEffect(() => {60    const el = ref.current;61    if (!el || typeof IntersectionObserver === "undefined") return;62    const io = new IntersectionObserver((entries) => {63      const entry = entries[entries.length - 1];64      if (entry) setInView(entry.isIntersecting);65    }, { rootMargin: "80px" });66    io.observe(el);67    return () => io.disconnect();68  }, [ref]);69  return inView;70}71 72export function ConicGradient({73  color1 = "#ff3d7f",74  color2 = "#ffb020",75  color3 = "#18d6a3",76  color4 = "#5b6cff",77  backgroundColor = "#07070b",78  speed = 1,79  blur = 60,80  spread = 55,81  interactive = true,82  ring = true,83  noise = true,84  height = 360,85  borderRadius = 20,86  children,87  className = "",88  style,89}: ConicGradientProps) {90  const rootRef = React.useRef<HTMLDivElement>(null);91  const reduced = useReducedMotionPref();92  const inView = useInView(rootRef);93  const safeSpeed = Math.max(0, speed);94  const running = !reduced && safeSpeed > 0 && inView;95 96  React.useEffect(() => {97    const el = rootRef.current;98    if (!el || !interactive || reduced) return;99    const target = { x: 0, y: 0 };100    const cur = { x: 0, y: 0 };101    let raf = 0;102    const step = () => {103      cur.x += (target.x - cur.x) * 0.08;104      cur.y += (target.y - cur.y) * 0.08;105      el.style.setProperty("--mf-cg-tx", `${cur.x.toFixed(2)}px`);106      el.style.setProperty("--mf-cg-ty", `${cur.y.toFixed(2)}px`);107      if (Math.abs(target.x - cur.x) > 0.05 || Math.abs(target.y - cur.y) > 0.05) {108        raf = requestAnimationFrame(step);109      } else {110        raf = 0;111      }112    };113    const kick = () => {114      if (!raf) raf = requestAnimationFrame(step);115    };116    const onMove = (e: PointerEvent) => {117      const rect = el.getBoundingClientRect();118      const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;119      const ny = ((e.clientY - rect.top) / rect.height) * 2 - 1;120      target.x = Math.max(-1, Math.min(1, nx)) * MAX_TILT_PX;121      target.y = Math.max(-1, Math.min(1, ny)) * MAX_TILT_PX;122      kick();123    };124    const onLeave = () => {125      target.x = 0;126      target.y = 0;127      kick();128    };129    el.addEventListener("pointermove", onMove);130    el.addEventListener("pointerleave", onLeave);131    return () => {132      el.removeEventListener("pointermove", onMove);133      el.removeEventListener("pointerleave", onLeave);134      cancelAnimationFrame(raf);135    };136  }, [interactive, reduced]);137 138  const conic = `conic-gradient(from 0deg, ${color1}, ${color2}, ${color3}, ${color4}, ${color1})`;139  const styleVars = {140    "--mf-cg-bg": backgroundColor,141    "--mf-cg-blur": `${Math.max(0, blur)}px`,142    "--mf-cg-size": `${Math.max(5, spread)}%`,143    "--mf-cg-conic": conic,144    "--mf-cg-dur": `${(BASE_TURN_S / (safeSpeed > 0 ? safeSpeed : 1)).toFixed(2)}s`,145    "--mf-cg-state": running ? "running" : "paused",146    height: `${height}px`,147    borderRadius: `${borderRadius}px`,148    ...style,149  } as React.CSSProperties;150 151  return (152    <div ref={rootRef} style={styleVars} className={`${styles.root} ${className}`.trim()}>153      <div aria-hidden="true" className={styles.stage}>154        <div className={styles.glow} />155        <div className={styles.core} />156        {ring ? <div className={styles.ring} /> : null}157        <div className={styles.center} />158      </div>159      {noise ? <div aria-hidden="true" className={styles.noise} /> : null}160      <div aria-hidden="true" className={styles.vignette} />161      <div className={styles.content}>162        {children ?? (163          <div className={styles.copy}>164            <h3 className={styles.title}>Conic Gradient</h3>165            <p className={styles.subtitle}>A slow-turning spectrum core with a soft halo.</p>166          </div>167        )}168      </div>169    </div>170  );171}