Staggered Glow Pricing Tiers
A three-tier pricing section whose cards reveal in a staggered spring cascade, with a rotating conic-gradient glow on the popular plan and prices that count up smoothly when you toggle monthly and annual billing.
npx shadcn@latest add https://webinnoventix.com/r/aprice-staggered-glow-tiers.json"use client";
import { useEffect, useState } from "react";
import {
motion,
AnimatePresence,
useSpring,
useTransform,
useReducedMotion,
} from "motion/react";
type Tier = {
name: string;
blurb: string;
monthly: number;
annual: number;
featured: boolean;
cta: string;
features: string[];
};
const tiers: Tier[] = [
{
name: "Starter",
blurb: "For individuals validating a first idea.",
monthly: 0,
annual: 0,
featured: false,
cta: "Start for free",
features: ["1 workspace", "5 GB storage", "Community support", "Basic analytics"],
},
{
name: "Growth",
blurb: "For teams shipping every single week.",
monthly: 29,
annual: 23,
featured: true,
cta: "Start free trial",
features: [
"Unlimited workspaces",
"250 GB storage",
"Priority support",
"Advanced analytics",
"Role based access",
"Workflow automations",
],
},
{
name: "Scale",
blurb: "For organisations with heavier demands.",
monthly: 59,
annual: 47,
featured: false,
cta: "Contact sales",
features: [
"Everything in Growth",
"2 TB storage",
"Dedicated manager",
"Audit logs",
"Single sign on",
],
},
];
function AnimatedPrice({ value, reduce }: { value: number; reduce: boolean }) {
const spring = useSpring(value, { stiffness: 150, damping: 24, mass: 0.6 });
const text = useTransform(spring, (v) => Math.round(v).toString());
useEffect(() => {
spring.set(value);
}, [value, spring]);
if (reduce) return <span>{value}</span>;
return <motion.span>{text}</motion.span>;
}
const container = {
hidden: {},
show: { transition: { staggerChildren: 0.12, delayChildren: 0.06 } },
};
const item = {
hidden: { opacity: 0, y: 34 },
show: {
opacity: 1,
y: 0,
transition: { type: "spring" as const, stiffness: 90, damping: 15 },
},
};
const billing = [
{ key: false, label: "Monthly" },
{ key: true, label: "Annual" },
];
export default function AnimatedGlowPricingTiers() {
const [annual, setAnnual] = useState(true);
const reduce = useReducedMotion() ?? false;
return (
<section className="relative overflow-hidden bg-zinc-50 px-6 py-20 dark:bg-zinc-950 md:py-28">
<style>{`
@keyframes aprice-shimmer {
0% { transform: translateX(-130%) skewX(-14deg); }
55%, 100% { transform: translateX(260%) skewX(-14deg); }
}
.aprice-shine { position: relative; overflow: hidden; }
.aprice-shine::after {
content: ""; position: absolute; top: 0; bottom: 0; width: 42%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,.55), transparent);
animation: aprice-shimmer 3.8s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.aprice-shine::after { display: none; animation: none; }
}
`}</style>
<div className="pointer-events-none absolute inset-x-0 top-0 -z-0 mx-auto h-64 max-w-3xl bg-[radial-gradient(closest-side,rgba(129,140,248,0.18),transparent)] blur-2xl dark:bg-[radial-gradient(closest-side,rgba(129,140,248,0.14),transparent)]" />
<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"
>
<span className="inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-white px-3 py-1 text-xs font-medium text-zinc-600 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
Simple, transparent pricing
</span>
<h2 className="mt-5 text-3xl font-bold tracking-tight text-balance text-zinc-900 sm:text-4xl dark:text-white">
Pricing that grows with your team
</h2>
<p className="mx-auto mt-3 max-w-lg text-zinc-600 dark:text-zinc-400">
Start free, upgrade when you are ready. Switch to annual billing and save around 20 per
cent on every plan.
</p>
</motion.div>
<div className="mt-8 flex flex-col items-center gap-3">
<div
role="tablist"
aria-label="Billing period"
className="relative inline-flex rounded-full border border-zinc-200 bg-white p-1 dark:border-zinc-800 dark:bg-zinc-900"
>
{billing.map((opt) => {
const isActive = annual === opt.key;
return (
<button
key={opt.label}
role="tab"
aria-selected={isActive}
onClick={() => setAnnual(opt.key)}
className="relative z-10 rounded-full px-6 py-2 text-sm font-semibold transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500"
>
{isActive && (
<motion.span
layoutId="aprice-glow-pill"
className="absolute inset-0 -z-10 rounded-full bg-zinc-900 dark:bg-white"
transition={{ type: "spring", stiffness: 420, damping: 34 }}
/>
)}
<span
className={
isActive
? "text-white dark:text-zinc-900"
: "text-zinc-600 dark:text-zinc-400"
}
>
{opt.label}
</span>
</button>
);
})}
</div>
<div className="h-5">
<AnimatePresence>
{annual && (
<motion.span
initial={{ opacity: 0, y: -6 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -6 }}
className="rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-semibold text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400"
>
You save 20 per cent
</motion.span>
)}
</AnimatePresence>
</div>
</div>
<motion.div
variants={container}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.2 }}
className="mt-12 grid gap-6 lg:grid-cols-3 lg:items-center"
>
{tiers.map((tier) => {
const price = annual ? tier.annual : tier.monthly;
return (
<motion.div
key={tier.name}
variants={item}
whileHover={reduce ? undefined : { y: -8 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
className={"relative " + (tier.featured ? "lg:-my-4 lg:z-10" : "")}
>
{tier.featured &&
(reduce ? (
<div
aria-hidden="true"
className="pointer-events-none absolute -inset-[3px] rounded-[27px] opacity-60 blur-md"
style={{ background: "linear-gradient(135deg,#6366f1,#a855f7,#ec4899)" }}
/>
) : (
<motion.div
aria-hidden="true"
className="pointer-events-none absolute -inset-[3px] rounded-[27px] opacity-70 blur-md"
style={{
background:
"conic-gradient(from 0deg,#6366f1,#a855f7,#ec4899,#f59e0b,#6366f1)",
}}
animate={{ rotate: 360 }}
transition={{ repeat: Infinity, duration: 8, ease: "linear" }}
/>
))}
<div
className={
"relative flex h-full flex-col rounded-3xl border p-8 " +
(tier.featured
? "border-transparent bg-white shadow-2xl dark:bg-zinc-900"
: "border-zinc-200 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-900/60")
}
>
{tier.featured && (
<span className="absolute -top-3 left-1/2 inline-flex -translate-x-1/2 items-center gap-1.5 rounded-full bg-gradient-to-r from-indigo-600 to-violet-600 px-3 py-1 text-xs font-semibold text-white shadow-lg">
<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-white opacity-75" />
)}
<span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-white" />
</span>
Most popular
</span>
)}
<h3 className="text-lg font-semibold text-zinc-900 dark:text-white">
{tier.name}
</h3>
<p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">{tier.blurb}</p>
<div className="mt-6 flex items-baseline gap-1">
<span className="text-5xl font-bold tracking-tight text-zinc-900 tabular-nums dark:text-white">
£
<AnimatedPrice value={price} reduce={reduce} />
</span>
<span className="text-sm text-zinc-500 dark:text-zinc-400">/mo</span>
</div>
<p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400">
{annual ? "Billed annually" : "Billed monthly"}
</p>
<a
href="#"
className={
"mt-6 inline-flex 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 " +
(tier.featured
? "aprice-shine bg-zinc-900 text-white hover:bg-zinc-800 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-200"
: "border border-zinc-200 bg-white text-zinc-900 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-white dark:hover:bg-zinc-800")
}
>
{tier.cta}
</a>
<ul className="mt-8 space-y-3 border-t border-zinc-100 pt-6 text-sm dark:border-zinc-800">
{tier.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-600 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>
);
})}
</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.

