Web InnoventixFreeCode

Spotlight Follow Background

Original · free

A dot-grid section where a spring-eased spotlight follows the cursor and reveals a hidden lattice of coloured dots.

byWeb InnoventixReact + Tailwind
spotlight followmouse follow backgroundcursor spotlightdot grid backgroundinteractive backgroundframer motion spotlight
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/bg-spotlight-follow.json
bg-spotlight-follow.tsx
"use client";

import { useRef, type MouseEvent } from "react";
import {
  motion,
  useMotionValue,
  useMotionTemplate,
  useSpring,
  useReducedMotion,
  type Variants,
} from "motion/react";

const dotImage = "radial-gradient(currentColor 1px, transparent 1px)";

const fade: Variants = {
  hidden: { opacity: 0, y: 22 },
  show: (i: number) => ({
    opacity: 1,
    y: 0,
    transition: { duration: 0.6, delay: i * 0.1, ease: [0.22, 1, 0.36, 1] },
  }),
};

export default function BgSpotlightFollow() {
  const ref = useRef<HTMLElement>(null);
  const reduce = useReducedMotion();

  const x = useMotionValue(50);
  const y = useMotionValue(40);
  const springX = useSpring(x, { stiffness: 140, damping: 22, mass: 0.4 });
  const springY = useSpring(y, { stiffness: 140, damping: 22, mass: 0.4 });

  const glow = useMotionTemplate`radial-gradient(420px circle at ${springX}% ${springY}%, rgba(99,102,241,0.28), transparent 60%)`;
  const revealMask = useMotionTemplate`radial-gradient(220px circle at ${springX}% ${springY}%, black 20%, transparent 75%)`;

  const handleMove = (event: MouseEvent<HTMLElement>) => {
    const el = ref.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    x.set(((event.clientX - rect.left) / rect.width) * 100);
    y.set(((event.clientY - rect.top) / rect.height) * 100);
  };

  return (
    <section
      ref={ref}
      onMouseMove={handleMove}
      className="relative isolate flex min-h-[640px] w-full items-center justify-center overflow-hidden bg-slate-50 px-6 py-24 dark:bg-slate-950"
    >
      {/* base dot grid */}
      <div
        aria-hidden="true"
        className="pointer-events-none absolute inset-0 -z-10 text-slate-900/10 dark:text-white/10"
        style={{ backgroundImage: dotImage, backgroundSize: "24px 24px" }}
      />

      {/* coloured dots revealed only under the spotlight */}
      <motion.div
        aria-hidden="true"
        className="pointer-events-none absolute inset-0 -z-10 text-indigo-500 dark:text-indigo-400"
        style={{
          backgroundImage: dotImage,
          backgroundSize: "24px 24px",
          maskImage: reduce ? undefined : revealMask,
          WebkitMaskImage: reduce ? undefined : revealMask,
          opacity: reduce ? 0 : 1,
        }}
      />

      {/* soft glow that trails the cursor */}
      <motion.div
        aria-hidden="true"
        className="pointer-events-none absolute inset-0 -z-10"
        style={{ background: reduce ? undefined : glow }}
      />

      <motion.div
        variants={fade}
        initial="hidden"
        whileInView="show"
        viewport={{ once: true, amount: 0.35 }}
        custom={0}
        className="relative mx-auto max-w-2xl text-center"
      >
        <motion.span
          variants={fade}
          custom={0}
          className="inline-flex items-center gap-2 rounded-full border border-slate-200/70 bg-white/70 px-4 py-1.5 text-sm font-medium text-slate-600 shadow-sm backdrop-blur dark:border-white/10 dark:bg-white/5 dark:text-slate-300"
        >
          <svg
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth={2}
            strokeLinecap="round"
            strokeLinejoin="round"
            aria-hidden="true"
            className="h-4 w-4 text-indigo-500 dark:text-indigo-400"
          >
            <circle cx="12" cy="12" r="4" />
            <path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19" />
          </svg>
          Move your cursor
        </motion.span>

        <motion.h1
          variants={fade}
          custom={1}
          className="mt-6 text-4xl font-semibold tracking-tight text-slate-900 sm:text-6xl dark:text-white"
        >
          Follow the light
        </motion.h1>

        <motion.p
          variants={fade}
          custom={2}
          className="mx-auto mt-5 max-w-xl text-base leading-relaxed text-slate-600 sm:text-lg dark:text-slate-400"
        >
          A spotlight tracks your pointer with a gentle spring, lighting up a
          hidden lattice of colour as it passes. It settles calmly to centre
          when you leave.
        </motion.p>

        <motion.div variants={fade} custom={3} className="mt-9">
          <a
            href="#"
            className="group inline-flex items-center gap-2 rounded-full bg-slate-900 px-6 py-3 text-sm font-semibold text-white shadow-lg transition hover:bg-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-900 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200 dark:focus-visible:ring-white dark:focus-visible:ring-offset-slate-950"
          >
            Explore the effect
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth={2}
              strokeLinecap="round"
              strokeLinejoin="round"
              aria-hidden="true"
              className="h-4 w-4 transition group-hover:translate-x-0.5"
            >
              <path d="M5 12h14M13 6l6 6-6 6" />
            </svg>
          </a>
        </motion.div>
      </motion.div>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →
Aurora Blobs Background

Aurora Blobs Background

Original

An animated hero section with soft, ever-drifting aurora gradient blobs floating behind staggered, blur-in content.

Retro Perspective Grid

Retro Perspective Grid

Original

A launch and waitlist section set over an infinite neon grid that scrolls into a glowing horizon with rising particles.

Conic Gradient Flow

Conic Gradient Flow

Original

A frosted hero backed by twin counter-rotating conic gradients, with flowing gradient text and an animated gradient-border stat card.

Dot Pattern Background

Dot Pattern Background

pattern.tsx

A reusable animated background for React and Tailwind: dot pattern background.

Flickering Grid Background

Flickering Grid Background

grid.tsx

A reusable animated background for React and Tailwind: flickering grid background.

Grid Pattern Background

Grid Pattern Background

pattern.tsx

A reusable animated background for React and Tailwind: grid pattern background.

Retro Grid Background

Retro Grid Background

grid.json

A reusable animated background for React and Tailwind: retro grid background.

Spotlight Hero

Spotlight Hero

Original

A centred hero with a soft radial spotlight, badge and dual call-to-action.

Split Hero

Split Hero

Original

A two-column hero pairing a headline and CTAs with a product mock and social proof.

Gradient Spotlight Hero

Gradient Spotlight Hero

Original

A minimal centred hero with a soft gradient-mesh backdrop, announcement pill and dual call-to-action buttons.

App Preview Hero

App Preview Hero

Original

A centred hero that pairs headline copy with a realistic product dashboard mock built entirely from markup, complete with browser chrome and a floating notification card.

Waitlist Capture Hero

Waitlist Capture Hero

Original

A dark, focused hero with an inline email capture form and avatar social proof, ready for pre-launch waitlists.