Spotlight Cursor Pricing Cards
Pricing cards that light up with a cursor-following radial spotlight, drift over floating background orbs and rise into view with a staggered spring entrance, plus a pulsing glow on the popular plan.
npx shadcn@latest add https://webinnoventix.com/r/aprice-spotlight-cursor-cards.json"use client";
import { type MouseEvent } from "react";
import {
motion,
useMotionValue,
useMotionTemplate,
useReducedMotion,
} from "motion/react";
type Plan = {
name: string;
blurb: string;
price: string;
period: string;
featured: boolean;
cta: string;
features: string[];
};
const plans: Plan[] = [
{
name: "Personal",
blurb: "Everything you need to launch on your own.",
price: "£0",
period: "forever",
featured: false,
cta: "Get started",
features: ["1 project", "Community support", "1 GB storage", "Standard templates"],
},
{
name: "Studio",
blurb: "For growing teams that ship together.",
price: "£38",
period: "per month",
featured: true,
cta: "Start free trial",
features: [
"Unlimited projects",
"Priority support",
"500 GB storage",
"Premium templates",
"Shared libraries",
"Version history",
],
},
{
name: "Agency",
blurb: "Advanced control for larger organisations.",
price: "£96",
period: "per month",
featured: false,
cta: "Talk to sales",
features: ["Everything in Studio", "Unlimited storage", "SSO and SCIM", "Custom roles", "SLA"],
},
];
const container = {
hidden: {},
show: { transition: { staggerChildren: 0.14, delayChildren: 0.08 } },
};
const item = {
hidden: { opacity: 0, y: 40, scale: 0.97 },
show: {
opacity: 1,
y: 0,
scale: 1,
transition: { type: "spring" as const, stiffness: 84, damping: 15 },
},
};
function SpotlightCard({ plan, reduce }: { plan: Plan; reduce: boolean }) {
const mx = useMotionValue(-200);
const my = useMotionValue(-200);
const spotlight = useMotionTemplate`radial-gradient(260px circle at ${mx}px ${my}px, rgba(129,140,248,0.35), transparent 72%)`;
function handleMove(e: MouseEvent<HTMLDivElement>) {
if (reduce) return;
const rect = e.currentTarget.getBoundingClientRect();
mx.set(e.clientX - rect.left);
my.set(e.clientY - rect.top);
}
return (
<motion.div
variants={item}
onMouseMove={handleMove}
whileHover={reduce ? undefined : { y: -6 }}
transition={{ type: "spring", stiffness: 300, damping: 22 }}
className={
"group relative flex h-full flex-col rounded-3xl border p-8 backdrop-blur-sm " +
(plan.featured
? "border-indigo-400/60 bg-white/80 shadow-xl dark:border-indigo-400/40 dark:bg-white/5"
: "border-zinc-200 bg-white/70 shadow-sm dark:border-white/10 dark:bg-white/[0.03]")
}
>
{plan.featured && !reduce && (
<motion.div
aria-hidden="true"
className="pointer-events-none absolute -inset-px -z-10 rounded-3xl"
style={{ boxShadow: "0 0 0 0 rgba(99,102,241,0)" }}
animate={{
boxShadow: [
"0 0 24px 0px rgba(99,102,241,0.15)",
"0 0 48px 6px rgba(129,140,248,0.35)",
"0 0 24px 0px rgba(99,102,241,0.15)",
],
}}
transition={{ repeat: Infinity, duration: 3.6, ease: "easeInOut" }}
/>
)}
<motion.div
aria-hidden="true"
className="pointer-events-none absolute inset-0 rounded-3xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
style={{ background: spotlight }}
/>
<div className="relative">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-zinc-900 dark:text-white">{plan.name}</h3>
{plan.featured && (
<span className="inline-flex items-center gap-1.5 rounded-full bg-indigo-500/10 px-2.5 py-1 text-xs font-semibold text-indigo-600 dark:text-indigo-300">
<span className="relative flex h-1.5 w-1.5">
{!reduce && (
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-indigo-400 opacity-75" />
)}
<span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-indigo-500" />
</span>
Popular
</span>
)}
</div>
<p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">{plan.blurb}</p>
<div className="mt-6 flex items-baseline gap-1.5">
<span className="text-5xl font-bold tracking-tight text-zinc-900 dark:text-white">
{plan.price}
</span>
<span className="text-sm text-zinc-500 dark:text-zinc-400">{plan.period}</span>
</div>
<a
href="#"
className={
"mt-6 inline-flex w-full items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500 " +
(plan.featured
? "bg-gradient-to-r from-indigo-600 to-violet-600 text-white shadow-lg shadow-indigo-500/25 hover:from-indigo-500 hover:to-violet-500"
: "border border-zinc-200 bg-white text-zinc-900 hover:bg-zinc-50 dark:border-white/15 dark:bg-white/5 dark:text-white dark:hover:bg-white/10")
}
>
{plan.cta}
</a>
<ul className="mt-8 space-y-3 border-t border-zinc-100 pt-6 text-sm dark:border-white/10">
{plan.features.map((feature) => (
<li
key={feature}
className="flex items-start gap-3 text-zinc-700 dark:text-zinc-300"
>
<svg
className="mt-0.5 h-5 w-5 shrink-0 text-indigo-500 dark:text-indigo-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M16.7 5.3a1 1 0 0 1 0 1.4l-7.5 7.5a1 1 0 0 1-1.4 0L3.3 10.7a1 1 0 1 1 1.4-1.4l3.8 3.8 6.8-6.8a1 1 0 0 1 1.4 0Z"
clipRule="evenodd"
/>
</svg>
<span>{feature}</span>
</li>
))}
</ul>
</div>
</motion.div>
);
}
export default function SpotlightCursorPricing() {
const reduce = useReducedMotion() ?? false;
return (
<section className="relative overflow-hidden bg-gradient-to-b from-white to-zinc-50 px-6 py-20 dark:from-zinc-950 dark:to-zinc-900 md:py-28">
<style>{`
@keyframes aprice-grad-pan {
0% { background-position: 0% 50%; }
100% { background-position: 200% 50%; }
}
.aprice-grad {
background-image: linear-gradient(90deg,#6366f1,#a855f7,#ec4899,#6366f1);
background-size: 200% auto;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: aprice-grad-pan 6s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.aprice-grad { animation: none; }
}
`}</style>
{!reduce && (
<>
<motion.div
aria-hidden="true"
className="pointer-events-none absolute -left-24 top-10 -z-0 h-72 w-72 rounded-full bg-indigo-400/25 blur-3xl dark:bg-indigo-500/20"
animate={{ x: [0, 40, 0], y: [0, 30, 0], scale: [1, 1.15, 1] }}
transition={{ repeat: Infinity, duration: 14, ease: "easeInOut" }}
/>
<motion.div
aria-hidden="true"
className="pointer-events-none absolute -right-24 bottom-0 -z-0 h-80 w-80 rounded-full bg-fuchsia-400/20 blur-3xl dark:bg-fuchsia-500/15"
animate={{ x: [0, -50, 0], y: [0, -30, 0], scale: [1.1, 1, 1.1] }}
transition={{ repeat: Infinity, duration: 16, ease: "easeInOut" }}
/>
</>
)}
<div className="relative mx-auto max-w-6xl">
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.6 }}
transition={{ duration: 0.5 }}
className="text-center"
>
<h2 className="text-3xl font-bold tracking-tight text-balance sm:text-4xl">
<span className="text-zinc-900 dark:text-white">Plans built to </span>
<span className="aprice-grad">move with you</span>
</h2>
<p className="mx-auto mt-3 max-w-lg text-zinc-600 dark:text-zinc-400">
Move your cursor across a card to light it up. Every plan includes a fourteen day trial,
no card required.
</p>
</motion.div>
<motion.div
variants={container}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.2 }}
className="mt-14 grid gap-6 lg:grid-cols-3"
>
{plans.map((plan) => (
<SpotlightCard key={plan.name} plan={plan} reduce={reduce} />
))}
</motion.div>
</div>
</section>
);
}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 quoteMore blocks
Simple Pricing
OriginalA three-tier pricing section with a highlighted popular plan.
Single Plan Pricing Card
OriginalA focused one-plan pricing card with price, trial badge, feature list, call to action and a money-back trust note, ideal when you sell a single flat-rate product.
Three Tier Pricing With Monthly Annual Toggle
OriginalA three-tier pricing grid with a pure-CSS monthly and annual billing toggle that swaps every price with no JavaScript, using a native checkbox and Tailwind group-has state.

