Web InnoventixFreeCode

Pricing Single Card

Original · free

A single pricing plan card with feature list and CTA.

byWeb InnoventixReact + Tailwind
cardpricingsinglecards
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/card-pricing-single.json
card-pricing-single.tsx
"use client";

import { useId, useState } from "react";
import { motion, useReducedMotion } from "motion/react";

type BillingCycle = "monthly" | "annual";

interface Feature {
  label: string;
  included: boolean;
}

const FEATURES: Feature[] = [
  { label: "Up to 25 team members", included: true },
  { label: "Unlimited public projects", included: true },
  { label: "Advanced analytics dashboard", included: true },
  { label: "Priority email & chat support", included: true },
  { label: "Custom domains with SSL", included: true },
  { label: "SSO & SCIM provisioning", included: false },
  { label: "Dedicated success manager", included: false },
];

const PRICING: Record<BillingCycle, { amount: number; suffix: string; note: string }> = {
  monthly: { amount: 29, suffix: "/mo", note: "Billed monthly, cancel anytime" },
  annual: { amount: 23, suffix: "/mo", note: "Billed $276 yearly — save 20%" },
};

function CheckIcon() {
  return (
    <svg viewBox="0 0 20 20" fill="none" className="h-3.5 w-3.5" aria-hidden="true">
      <path
        d="M4 10.5l3.5 3.5L16 5.5"
        stroke="currentColor"
        strokeWidth="2.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

function MinusIcon() {
  return (
    <svg viewBox="0 0 20 20" fill="none" className="h-3.5 w-3.5" aria-hidden="true">
      <path
        d="M5 10h10"
        stroke="currentColor"
        strokeWidth="2.5"
        strokeLinecap="round"
      />
    </svg>
  );
}

function SparkIcon() {
  return (
    <svg viewBox="0 0 24 24" fill="none" className="h-4 w-4" aria-hidden="true">
      <path
        d="M12 2.5l1.9 5.3 5.6.5-4.3 3.6 1.4 5.4L12 19.4l-4.6 2.4 1.4-5.4-4.3-3.6 5.6-.5L12 2.5z"
        fill="currentColor"
      />
    </svg>
  );
}

function SpinnerIcon() {
  return (
    <svg viewBox="0 0 24 24" fill="none" className="h-4 w-4" aria-hidden="true">
      <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="3" className="opacity-25" />
      <path
        d="M21 12a9 9 0 0 0-9-9"
        stroke="currentColor"
        strokeWidth="3"
        strokeLinecap="round"
      />
    </svg>
  );
}

function PricingCard() {
  const [cycle, setCycle] = useState<BillingCycle>("annual");
  const [status, setStatus] = useState<"idle" | "loading" | "done">("idle");
  const reduceMotion = useReducedMotion();
  const headingId = useId();
  const priceId = useId();

  const price = PRICING[cycle];
  const isAnnual = cycle === "annual";

  function handleSubscribe() {
    if (status !== "idle") return;
    setStatus("loading");
    window.setTimeout(() => {
      setStatus("done");
      window.setTimeout(() => setStatus("idle"), 2200);
    }, 1400);
  }

  return (
    <div
      className="group relative w-full max-w-sm rounded-3xl border border-slate-200 bg-white p-1 shadow-[0_1px_2px_rgba(15,23,42,0.06),0_12px_40px_-12px_rgba(15,23,42,0.18)] transition-shadow duration-300 hover:shadow-[0_1px_2px_rgba(15,23,42,0.08),0_24px_60px_-16px_rgba(79,70,229,0.28)] dark:border-slate-800 dark:bg-slate-900 dark:shadow-[0_1px_2px_rgba(0,0,0,0.4),0_18px_50px_-18px_rgba(0,0,0,0.7)]"
    >
      {/* gradient hairline top accent */}
      <div
        aria-hidden="true"
        className="pointer-events-none absolute inset-x-8 top-0 h-px bg-gradient-to-r from-transparent via-indigo-400/70 to-transparent dark:via-indigo-400/50"
      />

      <div className="relative rounded-[1.375rem] bg-white p-7 dark:bg-slate-900">
        <div className="flex items-start justify-between gap-4">
          <div>
            <div className="flex items-center gap-2">
              <span className="flex h-8 w-8 items-center justify-center rounded-xl bg-indigo-600 text-white shadow-sm shadow-indigo-600/30">
                <SparkIcon />
              </span>
              <h3
                id={headingId}
                className="text-base font-semibold tracking-tight text-slate-900 dark:text-white"
              >
                Pro
              </h3>
            </div>
            <p className="mt-3 text-sm leading-relaxed text-slate-500 dark:text-slate-400">
              For growing teams shipping real products — everything you need to scale.
            </p>
          </div>

          <span className="shrink-0 rounded-full bg-emerald-50 px-2.5 py-1 text-[0.7rem] font-semibold uppercase tracking-wide text-emerald-700 ring-1 ring-inset ring-emerald-600/20 dark:bg-emerald-500/10 dark:text-emerald-300 dark:ring-emerald-400/25">
            Popular
          </span>
        </div>

        {/* Billing toggle */}
        <div
          role="group"
          aria-label="Billing period"
          className="mt-6 grid grid-cols-2 gap-1 rounded-xl bg-slate-100 p-1 dark:bg-slate-800/80"
        >
          {(["monthly", "annual"] as BillingCycle[]).map((value) => {
            const active = cycle === value;
            return (
              <button
                key={value}
                type="button"
                aria-pressed={active}
                onClick={() => setCycle(value)}
                className={`relative rounded-lg px-3 py-1.5 text-sm font-medium capitalize outline-none transition-colors focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-100 dark:focus-visible:ring-offset-slate-800 ${
                  active
                    ? "bg-white text-slate-900 shadow-sm dark:bg-slate-950 dark:text-white"
                    : "text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
                }`}
              >
                {value}
                {value === "annual" && (
                  <span className="ml-1 text-[0.65rem] font-bold text-emerald-600 dark:text-emerald-400">
                    −20%
                  </span>
                )}
              </button>
            );
          })}
        </div>

        {/* Price */}
        <div className="mt-6 flex items-end gap-1" aria-live="polite">
          <motion.span
            key={cycle}
            id={priceId}
            initial={reduceMotion ? false : { opacity: 0, y: 6 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.28, ease: [0.22, 1, 0.36, 1] }}
            className="text-5xl font-semibold tracking-tight text-slate-900 tabular-nums dark:text-white"
          >
            ${price.amount}
          </motion.span>
          <span className="pb-1.5 text-base font-medium text-slate-400 dark:text-slate-500">
            {price.suffix}
          </span>
        </div>
        <p className="mt-1.5 text-xs text-slate-500 dark:text-slate-400">{price.note}</p>

        {/* CTA */}
        <button
          type="button"
          onClick={handleSubscribe}
          disabled={status === "loading"}
          aria-label={isAnnual ? "Subscribe to Pro, billed annually" : "Subscribe to Pro, billed monthly"}
          className="mt-6 flex w-full items-center justify-center gap-2 rounded-xl bg-indigo-600 px-4 py-3 text-sm font-semibold text-white shadow-sm shadow-indigo-600/30 outline-none transition-all duration-200 hover:bg-indigo-500 hover:shadow-md hover:shadow-indigo-600/40 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white active:scale-[0.985] disabled:cursor-not-allowed disabled:opacity-70 dark:focus-visible:ring-offset-slate-900"
        >
          {status === "loading" && (
            <span className="cpsingle-spin motion-reduce:animate-none">
              <SpinnerIcon />
            </span>
          )}
          {status === "loading"
            ? "Processing…"
            : status === "done"
              ? "You're all set ✓"
              : "Start 14-day free trial"}
        </button>
        <p className="mt-3 text-center text-xs text-slate-400 dark:text-slate-500">
          No credit card required
        </p>

        {/* Features */}
        <div className="my-6 h-px w-full bg-slate-100 dark:bg-slate-800" />
        <ul className="space-y-3" aria-label="Plan features">
          {FEATURES.map((feature) => (
            <li key={feature.label} className="flex items-center gap-3 text-sm">
              <span
                className={`flex h-5 w-5 shrink-0 items-center justify-center rounded-full ${
                  feature.included
                    ? "bg-indigo-50 text-indigo-600 ring-1 ring-inset ring-indigo-600/15 dark:bg-indigo-500/15 dark:text-indigo-300 dark:ring-indigo-400/20"
                    : "bg-slate-100 text-slate-400 dark:bg-slate-800 dark:text-slate-600"
                }`}
              >
                {feature.included ? <CheckIcon /> : <MinusIcon />}
              </span>
              <span
                className={
                  feature.included
                    ? "text-slate-700 dark:text-slate-200"
                    : "text-slate-400 line-through decoration-slate-300 dark:text-slate-600 dark:decoration-slate-700"
                }
              >
                {feature.label}
              </span>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default function CardPricingSingle() {
  return (
    <section className="relative w-full bg-slate-50 px-4 py-20 dark:bg-slate-950 sm:px-6 sm:py-28">
      <style>{`
        @keyframes cpsingle-spin {
          to { transform: rotate(360deg); }
        }
        .cpsingle-spin {
          display: inline-flex;
          animation: cpsingle-spin 0.7s linear infinite;
        }
        @media (prefers-reduced-motion: reduce) {
          .cpsingle-spin { animation: none; }
        }
      `}</style>

      <div className="mx-auto flex max-w-md flex-col items-center">
        <div className="mb-10 text-center">
          <span className="inline-flex items-center rounded-full bg-indigo-50 px-3 py-1 text-xs font-medium text-indigo-700 ring-1 ring-inset ring-indigo-600/15 dark:bg-indigo-500/10 dark:text-indigo-300 dark:ring-indigo-400/20">
            Pricing
          </span>
          <h2 className="mt-4 text-2xl font-semibold tracking-tight text-slate-900 dark:text-white sm:text-3xl">
            One plan, everything included
          </h2>
          <p className="mt-3 text-sm text-slate-500 dark:text-slate-400">
            Toggle billing and subscribe — the card is fully interactive.
          </p>
        </div>

        <PricingCard />
      </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 →