Web InnoventixFreeCode

Spotlight Follow Cards

Original · free

Feature cards where a soft radial spotlight and a glowing border chase the cursor on hover, with a staggered scroll reveal and an animated shimmer line.

byWeb InnoventixReact + Tailwind
spotlight cardcursor follow glowframer motion cardshover spotlight effectanimated border cardinteractive feature cards
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/card-spotlight-follow.json
card-spotlight-follow.tsx
"use client";

import { useRef, useState, type PointerEvent as ReactPointerEvent } from "react";
import { motion, useMotionTemplate, useMotionValue } from "motion/react";

const EASE = [0.16, 1, 0.3, 1] as const;

type SpotItem = {
  title: string;
  copy: string;
  icon: "shield" | "bolt" | "chart";
};

const ITEMS: SpotItem[] = [
  {
    title: "Guarded by design",
    copy: "Encryption at rest and in transit, single sign-on and audit logs come switched on from day one.",
    icon: "shield",
  },
  {
    title: "Quick off the mark",
    copy: "Edge delivery and smart caching keep every screen under a hundred milliseconds worldwide.",
    icon: "bolt",
  },
  {
    title: "Clear at a glance",
    copy: "Dashboards translate raw events into the two or three numbers your team actually acts on.",
    icon: "chart",
  },
];

const PATHS: Record<SpotItem["icon"], string> = {
  shield: "M12 2 4 6v6c0 4 3 7 8 10 5-3 8-6 8-10V6z",
  bolt: "M13 2 3 14h7l-1 8 10-12h-7z",
  chart: "M4 20V10M10 20V4M16 20v-7M22 20H2",
};

function SpotlightCard({ item, index }: { item: SpotItem; index: number }) {
  const ref = useRef<HTMLElement>(null);
  const [hover, setHover] = useState(false);
  const mx = useMotionValue(0);
  const my = useMotionValue(0);

  const fill = useMotionTemplate`radial-gradient(240px circle at ${mx}px ${my}px, rgba(99,102,241,0.20), transparent 72%)`;
  const edge = useMotionTemplate`radial-gradient(220px circle at ${mx}px ${my}px, rgba(129,140,248,0.85), transparent 65%)`;

  function move(e: ReactPointerEvent<HTMLElement>) {
    const el = ref.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    mx.set(e.clientX - rect.left);
    my.set(e.clientY - rect.top);
  }

  return (
    <motion.article
      ref={ref}
      onPointerMove={move}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
      initial={{ opacity: 0, y: 40 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true, margin: "-60px" }}
      transition={{ duration: 0.6, delay: index * 0.12, ease: EASE }}
      className="group relative rounded-2xl"
    >
      {/* glowing border that tracks the cursor */}
      <motion.div
        aria-hidden="true"
        style={{ background: edge, opacity: hover ? 1 : 0 }}
        className="pointer-events-none absolute inset-0 rounded-2xl transition-opacity duration-300"
      />

      <div className="relative m-px overflow-hidden rounded-[15px] border border-zinc-200 bg-white p-7 dark:border-zinc-800 dark:bg-zinc-900">
        {/* soft interior spotlight */}
        <motion.div
          aria-hidden="true"
          style={{ background: fill, opacity: hover ? 1 : 0 }}
          className="pointer-events-none absolute inset-0 transition-opacity duration-300"
        />
        {/* shimmer hairline along the top */}
        <div
          aria-hidden="true"
          className="csf-shimmer pointer-events-none absolute inset-x-0 top-0 h-px"
        />

        <div className="relative">
          <span className="inline-flex h-12 w-12 items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 ring-1 ring-indigo-100 transition-transform duration-300 group-hover:-translate-y-0.5 dark:bg-indigo-500/10 dark:text-indigo-300 dark:ring-indigo-500/20">
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth={1.7}
              strokeLinecap="round"
              strokeLinejoin="round"
              className="h-6 w-6"
              aria-hidden="true"
            >
              <path d={PATHS[item.icon]} />
            </svg>
          </span>

          <h3 className="mt-5 text-lg font-bold text-zinc-900 dark:text-white">
            {item.title}
          </h3>
          <p className="mt-2 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
            {item.copy}
          </p>

          <a
            href="#"
            className="mt-5 inline-flex items-center gap-1.5 text-sm font-semibold text-indigo-600 transition-colors hover:text-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-indigo-300 dark:focus-visible:ring-offset-zinc-900"
          >
            Learn more
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth={2}
              strokeLinecap="round"
              strokeLinejoin="round"
              className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1"
              aria-hidden="true"
            >
              <path d="M5 12h14M13 6l6 6-6 6" />
            </svg>
          </a>
        </div>
      </div>
    </motion.article>
  );
}

export default function CardSpotlightFollow() {
  return (
    <section className="bg-white px-6 py-20 dark:bg-zinc-950 md:py-28">
      <style>{`
        @keyframes csf-shimmer {
          0% { background-position: -150% 0; }
          100% { background-position: 250% 0; }
        }
        .csf-shimmer {
          background-image: linear-gradient(90deg, transparent, rgba(129,140,248,0.9), transparent);
          background-size: 60% 100%;
          background-repeat: no-repeat;
          animation: csf-shimmer 3.4s linear infinite;
        }
        @media (prefers-reduced-motion: reduce) {
          .csf-shimmer { animation: none; opacity: 0.4; }
        }
      `}</style>

      <div className="mx-auto max-w-6xl">
        <div className="mx-auto max-w-2xl text-center">
          <motion.h2
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.6, ease: EASE }}
            className="text-balance text-3xl font-bold tracking-tight text-zinc-900 sm:text-4xl dark:text-white"
          >
            A spotlight that chases your cursor
          </motion.h2>
          <motion.p
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.6, delay: 0.1, ease: EASE }}
            className="mx-auto mt-4 max-w-lg text-zinc-600 dark:text-zinc-400"
          >
            Hover any card and a soft light plus a glowing border follow the
            pointer, drawing attention exactly where you are looking.
          </motion.p>
        </div>

        <div className="mt-14 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
          {ITEMS.map((item, i) => (
            <SpotlightCard key={item.title} item={item} index={i} />
          ))}
        </div>
      </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 →
3D Tilt Cards

3D Tilt Cards

Original

A responsive grid of cards that tilt in 3D toward your cursor with a light glare and lifted depth layers, powered by Framer Motion springs.

3D Flip Cards

3D Flip Cards

Original

Pricing-style cards that flip in 3D on hover or tap to reveal details on the back, keyboard accessible with a staggered entrance animation.

Animated Gradient Border Cards

Animated Gradient Border Cards

Original

Cards wrapped in a live conic gradient border that rotates and speeds up on hover, with a shimmer sweep and a scrolling tag marquee.

Glow Effect Card

Glow Effect Card

primitives

A reusable glow effect card for React and Tailwind, with hover and motion.

Neon Gradient Card

Neon Gradient Card

gradient-card.tsx

A reusable neon gradient card for React and Tailwind, with hover and motion.

Spotlight Magic Card

Spotlight Magic Card

card.tsx

A reusable spotlight magic card for React and Tailwind, with hover and motion.

Tilt Spotlight Card

Tilt Spotlight Card

primitives

A reusable tilt spotlight card for React and Tailwind, with hover and motion.

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.