Toggle Annual Pricing
Original · freepricing with monthly/annual toggle
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/pricex-toggle-annual.json"use client";
import { useId, useState } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
type Plan = {
id: string;
name: string;
blurb: string;
monthly: number;
annual: number;
featured: boolean;
cta: string;
features: string[];
};
const PLANS: Plan[] = [
{
id: "starter",
name: "Starter",
blurb: "For solo builders shipping their very first product.",
monthly: 19,
annual: 15,
featured: false,
cta: "Start free",
features: [
"Up to 3 active projects",
"10,000 tracked events / month",
"7-day analytics retention",
"Community + docs support",
"Single workspace",
],
},
{
id: "growth",
name: "Growth",
blurb: "For teams that outgrew spreadsheets and need real leverage.",
monthly: 49,
annual: 39,
featured: true,
cta: "Start 14-day trial",
features: [
"Unlimited projects",
"250,000 tracked events / month",
"1-year analytics retention",
"Priority email support",
"Role-based access control",
"Custom domains & webhooks",
],
},
{
id: "scale",
name: "Scale",
blurb: "For orgs with security, volume, and compliance demands.",
monthly: 99,
annual: 79,
featured: false,
cta: "Talk to sales",
features: [
"Everything in Growth",
"5,000,000 tracked events / month",
"Unlimited retention",
"Dedicated success manager",
"SSO / SAML & audit logs",
"99.9% uptime SLA",
],
},
];
function Check({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 20 20"
fill="none"
aria-hidden="true"
className={className}
>
<path
d="M4 10.5l3.5 3.5L16 6"
stroke="currentColor"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function Spark({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" className={className}>
<path d="M12 2l1.9 5.7L19 9.6l-4.6 2.7L13 18l-1-4.9L7 12l4.4-1.4L12 2z" />
</svg>
);
}
export default function PricexToggleAnnual() {
const [annual, setAnnual] = useState(true);
const reduce = useReducedMotion();
const groupId = useId();
const spring = reduce
? { duration: 0 }
: { type: "spring" as const, stiffness: 520, damping: 34 };
return (
<section className="relative w-full overflow-hidden bg-slate-50 px-5 py-24 text-slate-900 sm:px-8 sm:py-32 dark:bg-slate-950 dark:text-slate-100">
<style>{`
@keyframes pxta-drift {
0%, 100% { transform: translate3d(0,0,0) scale(1); }
50% { transform: translate3d(0,-28px,0) scale(1.08); }
}
@keyframes pxta-sheen {
0% { background-position: 0% 50%; }
100% { background-position: 200% 50%; }
}
.pxta-blob { animation: pxta-drift 16s ease-in-out infinite; }
.pxta-blob-slow { animation: pxta-drift 22s ease-in-out infinite; }
.pxta-ring {
background: linear-gradient(115deg, #6366f1, #a855f7, #6366f1, #22d3ee);
background-size: 200% 200%;
animation: pxta-sheen 6s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.pxta-blob, .pxta-blob-slow, .pxta-ring { animation: none !important; }
}
`}</style>
{/* atmosphere */}
<div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="pxta-blob absolute -left-24 -top-24 h-96 w-96 rounded-full bg-indigo-400/25 blur-3xl dark:bg-indigo-600/20" />
<div className="pxta-blob-slow absolute -bottom-32 right-0 h-[26rem] w-[26rem] rounded-full bg-violet-400/20 blur-3xl dark:bg-violet-700/20" />
<div
className="absolute inset-0 opacity-[0.04] dark:opacity-[0.06]"
style={{
backgroundImage:
"linear-gradient(currentColor 1px, transparent 1px), linear-gradient(90deg, currentColor 1px, transparent 1px)",
backgroundSize: "56px 56px",
}}
/>
</div>
<div className="relative mx-auto max-w-6xl">
{/* header */}
<div className="mx-auto max-w-2xl text-center">
<span className="inline-flex items-center gap-2 rounded-full border border-indigo-500/20 bg-indigo-500/10 px-3.5 py-1.5 text-xs font-semibold uppercase tracking-[0.18em] text-indigo-600 dark:border-indigo-400/20 dark:bg-indigo-400/10 dark:text-indigo-300">
<span className="h-1.5 w-1.5 rounded-full bg-indigo-500 dark:bg-indigo-400" />
Cascade Pricing
</span>
<h2 className="mt-6 text-4xl font-semibold tracking-tight text-slate-900 sm:text-5xl dark:text-white">
Pricing that scales
<span className="block bg-gradient-to-r from-indigo-600 via-violet-600 to-sky-500 bg-clip-text text-transparent dark:from-indigo-400 dark:via-violet-400 dark:to-sky-400">
with your ambition
</span>
</h2>
<p className="mt-5 text-lg leading-relaxed text-slate-600 dark:text-slate-400">
Straightforward plans for every stage of growth. No hidden seats, no
surprise overages. Switch to annual and pocket two months.
</p>
</div>
{/* toggle */}
<div className="mt-10 flex flex-col items-center gap-3">
<div
role="group"
aria-label="Billing period"
className="relative inline-flex items-center rounded-full border border-slate-200 bg-white p-1 shadow-sm dark:border-slate-800 dark:bg-slate-900"
>
<motion.span
aria-hidden="true"
className="absolute inset-y-1 left-1 w-28 rounded-full bg-gradient-to-b from-slate-900 to-slate-700 shadow-md sm:w-32 dark:from-white dark:to-slate-200"
animate={{ x: annual ? "100%" : "0%" }}
transition={spring}
/>
<button
type="button"
onClick={() => setAnnual(false)}
aria-pressed={!annual}
aria-controls={groupId}
className={`relative z-10 w-28 rounded-full py-2.5 text-sm font-semibold transition-colors sm:w-32 ${
annual
? "text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200"
: "text-white dark:text-slate-900"
}`}
>
Monthly
</button>
<button
type="button"
onClick={() => setAnnual(true)}
aria-pressed={annual}
aria-controls={groupId}
className={`relative z-10 w-28 rounded-full py-2.5 text-sm font-semibold transition-colors sm:w-32 ${
annual
? "text-white dark:text-slate-900"
: "text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200"
}`}
>
Annual
</button>
</div>
<p className="flex items-center gap-1.5 text-sm text-emerald-600 dark:text-emerald-400">
<Spark className="h-4 w-4" />
<span className="font-medium">Save 20% — two months free on annual billing.</span>
</p>
</div>
{/* cards */}
<div
id={groupId}
className="mt-14 grid grid-cols-1 gap-6 lg:grid-cols-3 lg:items-start"
>
{PLANS.map((plan) => {
const price = annual ? plan.annual : plan.monthly;
const yearTotal = plan.annual * 12;
return (
<div
key={plan.id}
className={`relative rounded-3xl p-[1px] ${
plan.featured ? "pxta-ring lg:-mt-4 lg:mb-4" : ""
}`}
>
<div
className={`relative flex h-full flex-col rounded-[calc(1.5rem-1px)] p-7 sm:p-8 ${
plan.featured
? "bg-white shadow-2xl shadow-violet-500/10 dark:bg-slate-900"
: "border border-slate-200 bg-white/70 shadow-sm backdrop-blur-sm dark:border-slate-800 dark:bg-slate-900/60"
}`}
>
{plan.featured && (
<span className="absolute -top-3 right-7 inline-flex 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">
<Spark className="h-3.5 w-3.5" />
Most popular
</span>
)}
<div>
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">
{plan.name}
</h3>
<p className="mt-1.5 min-h-[2.75rem] text-sm leading-relaxed text-slate-500 dark:text-slate-400">
{plan.blurb}
</p>
</div>
{/* price */}
<div className="mt-6 flex items-end gap-1">
<span className="pb-1 text-2xl font-medium text-slate-400 dark:text-slate-500">
$
</span>
<div className="relative h-14 overflow-hidden">
<AnimatePresence initial={false} mode="popLayout">
<motion.span
key={price}
initial={reduce ? false : { y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={reduce ? { opacity: 0 } : { y: -20, opacity: 0 }}
transition={reduce ? { duration: 0 } : { duration: 0.25, ease: "easeOut" }}
className="block text-6xl font-semibold tracking-tight tabular-nums text-slate-900 dark:text-white"
>
{price}
</motion.span>
</AnimatePresence>
</div>
<span className="pb-2 text-sm font-medium text-slate-500 dark:text-slate-400">
/ mo
</span>
</div>
<p className="mt-2 h-5 text-sm text-slate-500 dark:text-slate-400">
{annual ? (
<>
<span className="font-medium text-slate-700 dark:text-slate-300">
${yearTotal}
</span>{" "}
billed yearly
</>
) : (
"billed monthly, cancel anytime"
)}
</p>
{/* cta */}
<button
type="button"
className={`mt-7 w-full rounded-xl px-5 py-3 text-sm font-semibold transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-900 ${
plan.featured
? "bg-gradient-to-r from-indigo-600 to-violet-600 text-white shadow-lg shadow-indigo-500/25 hover:-translate-y-0.5 hover:shadow-xl focus-visible:ring-indigo-500"
: "border border-slate-300 bg-white text-slate-900 hover:border-slate-400 hover:bg-slate-50 focus-visible:ring-slate-400 dark:border-slate-700 dark:bg-slate-800 dark:text-white dark:hover:bg-slate-700"
}`}
>
{plan.cta}
</button>
{/* divider */}
<div className="my-7 h-px w-full bg-gradient-to-r from-transparent via-slate-200 to-transparent dark:via-slate-800" />
{/* features */}
<ul className="flex flex-col gap-3.5">
{plan.features.map((feature) => (
<li key={feature} className="flex items-start gap-3 text-sm">
<span
className={`mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full ${
plan.featured
? "bg-indigo-100 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-300"
: "bg-emerald-100 text-emerald-600 dark:bg-emerald-500/15 dark:text-emerald-400"
}`}
>
<Check className="h-3 w-3" />
</span>
<span className="leading-relaxed text-slate-600 dark:text-slate-300">
{feature}
</span>
</li>
))}
</ul>
</div>
</div>
);
})}
</div>
{/* footnote */}
<p className="mt-12 text-center text-sm text-slate-500 dark:text-slate-400">
All plans include SSL, unlimited team members, and a 30-day money-back
guarantee. Prices in USD, excluding local taxes.
</p>
</div>
</section>
);
}Dependencies
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 quoteSimilar components
Browse all →
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.

Plan Feature Comparison Table
OriginalA responsive, semantic feature comparison table across three plans with grouped rows, tick and dash indicators and screen-reader labels so visitors can weigh every feature side by side.

Staggered Glow Pricing Tiers
OriginalA 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.

Spotlight Cursor Pricing Cards
OriginalPricing 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.

Highlight Follow Pricing Plans
OriginalA pricing row where an animated gradient highlight glides via shared layout animation to whichever plan you hover, a three-way billing switch slides its pill, prices recount instantly and a perks marquee scrolls beneath.

Three Tier Pricing
Originalthree-tier pricing cards

Single Highlight Pricing
Originalsingle highlighted plan

Comparison Table Pricing
Originalpricing comparison table

Cards Gradient Pricing
Originalgradient pricing cards

Minimal Pricing
Originalminimal pricing tiers

