AIEO-ready websites that get cited by AITalk to us

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.

React + Tailwindnpm: motionanimated pricingframer motion pricingstaggered revealpricing toggleglow card
Original · free to use
npx shadcn@latest add https://webinnoventix.com/r/aprice-staggered-glow-tiers.json
aprice-staggered-glow-tiers.tsx
"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">
                      &pound;
                      <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 quote

More blocks

All components
Chat with us