Web InnoventixFreeCode

Toggle Annual Pricing

Original · free

pricing with monthly/annual toggle

byWeb InnoventixReact + Tailwind
pricextoggleannualpricing
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/pricex-toggle-annual.json
pricex-toggle-annual.tsx
"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

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 →
Simple Pricing

Simple Pricing

Original

A three-tier pricing section with a highlighted popular plan.

Single Plan Pricing Card

Single Plan Pricing Card

Original

A 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

Three Tier Pricing With Monthly Annual Toggle

Original

A 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

Plan Feature Comparison Table

Original

A 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

Staggered Glow Pricing Tiers

Original

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.

Spotlight Cursor Pricing Cards

Spotlight Cursor Pricing Cards

Original

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.

Highlight Follow Pricing Plans

Highlight Follow Pricing Plans

Original

A 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

Three Tier Pricing

Original

three-tier pricing cards

Single Highlight Pricing

Single Highlight Pricing

Original

single highlighted plan

Comparison Table Pricing

Comparison Table Pricing

Original

pricing comparison table

Cards Gradient Pricing

Cards Gradient Pricing

Original

gradient pricing cards

Minimal Pricing

Minimal Pricing

Original

minimal pricing tiers