Web InnoventixFreeCode

Two Col Pricing

Original · free

two-column pricing

byWeb InnoventixReact + Tailwind
pricextwocolpricing
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-two-col.json
pricex-two-col.tsx
"use client";

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

type BillingCycle = "monthly" | "annual";

type PlanFeature = {
  label: string;
  included: boolean;
};

type Plan = {
  id: string;
  name: string;
  tagline: string;
  monthly: number;
  annual: number;
  featured: boolean;
  badge?: string;
  cta: string;
  features: PlanFeature[];
};

const PLANS: Plan[] = [
  {
    id: "starter",
    name: "Solo",
    tagline: "Everything one maker needs to ship and stay organized.",
    monthly: 12,
    annual: 9,
    featured: false,
    cta: "Start free trial",
    features: [
      { label: "Up to 3 active projects", included: true },
      { label: "5 GB encrypted storage", included: true },
      { label: "Unlimited public share links", included: true },
      { label: "Community support (48h)", included: true },
      { label: "Version history — 7 days", included: true },
      { label: "Priority render queue", included: false },
      { label: "Team roles & permissions", included: false },
      { label: "SSO / SAML sign-in", included: false },
    ],
  },
  {
    id: "team",
    name: "Studio",
    tagline: "For small teams that ship weekly and need real control.",
    monthly: 39,
    annual: 31,
    featured: true,
    badge: "Most picked",
    cta: "Upgrade to Studio",
    features: [
      { label: "Unlimited active projects", included: true },
      { label: "1 TB encrypted storage", included: true },
      { label: "Unlimited public share links", included: true },
      { label: "Priority support (4h)", included: true },
      { label: "Version history — 1 year", included: true },
      { label: "Priority render queue", included: true },
      { label: "Team roles & permissions", included: true },
      { label: "SSO / SAML sign-in", included: true },
    ],
  },
];

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

function DashIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 20 20"
      fill="none"
      stroke="currentColor"
      strokeWidth={2.2}
      strokeLinecap="round"
      className={className}
      aria-hidden="true"
    >
      <path d="M5 10h10" />
    </svg>
  );
}

function SparkIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden="true">
      <path
        d="M12 2.5l1.9 5.6a3 3 0 001.9 1.9l5.6 1.9-5.6 1.9a3 3 0 00-1.9 1.9L12 21.5l-1.9-5.6a3 3 0 00-1.9-1.9L2.6 12l5.6-1.9a3 3 0 001.9-1.9L12 2.5z"
        fill="currentColor"
      />
    </svg>
  );
}

export default function PricexTwoCol() {
  const reduceMotion = useReducedMotion();
  const [cycle, setCycle] = useState<BillingCycle>("annual");
  const groupId = useId();
  const isAnnual = cycle === "annual";

  return (
    <section className="relative w-full overflow-hidden bg-slate-50 px-5 py-24 text-slate-900 sm:px-8 dark:bg-slate-950 dark:text-slate-50">
      <style>{`
        @keyframes pricex2c-float {
          0%, 100% { transform: translate3d(0, 0, 0); }
          50% { transform: translate3d(0, -14px, 0); }
        }
        @keyframes pricex2c-drift {
          0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
          50% { transform: translate3d(0, 20px, 0) scale(1.06); }
        }
        @keyframes pricex2c-sheen {
          0% { background-position: 0% 50%; }
          100% { background-position: 200% 50%; }
        }
        .pricex2c-float { animation: pricex2c-float 9s ease-in-out infinite; }
        .pricex2c-drift { animation: pricex2c-drift 13s ease-in-out infinite; }
        .pricex2c-sheen {
          background-size: 200% auto;
          animation: pricex2c-sheen 6s linear infinite;
        }
        @media (prefers-reduced-motion: reduce) {
          .pricex2c-float,
          .pricex2c-drift,
          .pricex2c-sheen { animation: none !important; }
        }
      `}</style>

      {/* decorative backdrop */}
      <div
        aria-hidden="true"
        className="pointer-events-none absolute inset-0 -z-10"
      >
        <div className="pricex2c-float absolute -left-24 top-10 h-72 w-72 rounded-full bg-gradient-to-br from-indigo-400/40 to-violet-500/30 blur-3xl dark:from-indigo-600/30 dark:to-violet-700/20" />
        <div className="pricex2c-drift absolute -right-20 bottom-0 h-80 w-80 rounded-full bg-gradient-to-br from-sky-300/40 to-emerald-400/20 blur-3xl dark:from-sky-700/25 dark:to-emerald-800/15" />
        <div className="absolute inset-0 bg-[radial-gradient(circle_at_1px_1px,rgba(15,23,42,0.05)_1px,transparent_0)] [background-size:22px_22px] dark:bg-[radial-gradient(circle_at_1px_1px,rgba(226,232,240,0.06)_1px,transparent_0)]" />
      </div>

      <div className="mx-auto max-w-5xl">
        {/* header */}
        <div className="mx-auto max-w-2xl text-center">
          <span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white/70 px-4 py-1.5 text-xs font-semibold uppercase tracking-[0.14em] text-indigo-600 shadow-sm backdrop-blur dark:border-slate-800 dark:bg-slate-900/60 dark:text-indigo-300">
            <SparkIcon className="h-3.5 w-3.5" />
            Simple pricing
          </span>
          <h2 className="mt-6 text-balance text-4xl font-extrabold tracking-tight sm:text-5xl">
            Two plans. Zero guesswork.
          </h2>
          <p className="mx-auto mt-4 max-w-xl text-pretty text-base leading-relaxed text-slate-600 dark:text-slate-400">
            Start solo and grow into a studio. Switch, pause, or cancel any time —
            no sales calls, no hidden seats, no surprise usage bills.
          </p>

          {/* billing toggle */}
          <div
            role="radiogroup"
            aria-label="Billing cycle"
            className="mx-auto mt-9 inline-flex items-center gap-1 rounded-full border border-slate-200 bg-white p-1 shadow-sm dark:border-slate-800 dark:bg-slate-900"
          >
            {(["monthly", "annual"] as const).map((option) => {
              const active = cycle === option;
              return (
                <button
                  key={option}
                  type="button"
                  role="radio"
                  aria-checked={active}
                  onClick={() => setCycle(option)}
                  className={`relative rounded-full px-5 py-2 text-sm font-semibold outline-none transition-colors focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-900 ${
                    active
                      ? "text-white"
                      : "text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100"
                  }`}
                >
                  {active && (
                    <motion.span
                      layoutId={`${groupId}-cycle-pill`}
                      transition={
                        reduceMotion
                          ? { duration: 0 }
                          : { type: "spring", stiffness: 420, damping: 34 }
                      }
                      className="absolute inset-0 -z-10 rounded-full bg-slate-900 dark:bg-indigo-500"
                    />
                  )}
                  <span className="capitalize">{option}</span>
                  {option === "annual" && (
                    <span
                      className={`ml-2 rounded-full px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide ${
                        active
                          ? "bg-white/20 text-white"
                          : "bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300"
                      }`}
                    >
                      -22%
                    </span>
                  )}
                </button>
              );
            })}
          </div>
        </div>

        {/* plans */}
        <div className="mt-14 grid gap-6 md:grid-cols-2 md:items-stretch">
          {PLANS.map((plan, index) => {
            const price = isAnnual ? plan.annual : plan.monthly;
            return (
              <motion.div
                key={plan.id}
                initial={reduceMotion ? false : { opacity: 0, y: 26 }}
                whileInView={reduceMotion ? undefined : { opacity: 1, y: 0 }}
                viewport={{ once: true, amount: 0.3 }}
                transition={{
                  duration: 0.5,
                  delay: reduceMotion ? 0 : index * 0.08,
                  ease: [0.22, 1, 0.36, 1],
                }}
                className={`group relative flex flex-col rounded-3xl p-7 sm:p-8 ${
                  plan.featured
                    ? "bg-slate-900 text-slate-100 shadow-2xl shadow-indigo-500/20 ring-1 ring-slate-800 dark:bg-slate-900 dark:ring-indigo-500/40"
                    : "bg-white text-slate-900 shadow-xl shadow-slate-900/5 ring-1 ring-slate-200 dark:bg-slate-900/50 dark:text-slate-100 dark:ring-slate-800"
                }`}
              >
                {plan.featured && (
                  <>
                    <div
                      aria-hidden="true"
                      className="pricex2c-sheen pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-indigo-400 to-transparent"
                    />
                    {plan.badge && (
                      <span className="absolute -top-3 right-7 inline-flex items-center gap-1 rounded-full bg-gradient-to-r from-indigo-500 to-violet-500 px-3 py-1 text-[11px] font-bold uppercase tracking-wide text-white shadow-lg">
                        <SparkIcon className="h-3 w-3" />
                        {plan.badge}
                      </span>
                    )}
                  </>
                )}

                <div className="flex items-baseline justify-between">
                  <h3 className="text-lg font-bold tracking-tight">{plan.name}</h3>
                  <span
                    className={`text-xs font-semibold uppercase tracking-wider ${
                      plan.featured
                        ? "text-indigo-300"
                        : "text-slate-400 dark:text-slate-500"
                    }`}
                  >
                    {isAnnual ? "billed yearly" : "billed monthly"}
                  </span>
                </div>

                <p
                  className={`mt-2 text-sm leading-relaxed ${
                    plan.featured
                      ? "text-slate-400"
                      : "text-slate-500 dark:text-slate-400"
                  }`}
                >
                  {plan.tagline}
                </p>

                <div className="mt-6 flex items-end gap-1">
                  <span className="text-2xl font-semibold text-slate-400 dark:text-slate-500">
                    $
                  </span>
                  <motion.span
                    key={`${plan.id}-${price}`}
                    initial={reduceMotion ? false : { opacity: 0, y: 8 }}
                    animate={{ opacity: 1, y: 0 }}
                    transition={{ duration: 0.3, ease: "easeOut" }}
                    className={`text-5xl font-extrabold tracking-tight ${
                      plan.featured
                        ? "pricex2c-sheen bg-gradient-to-r from-white via-indigo-200 to-white bg-clip-text text-transparent"
                        : ""
                    }`}
                  >
                    {price}
                  </motion.span>
                  <span
                    className={`mb-1.5 text-sm font-medium ${
                      plan.featured
                        ? "text-slate-400"
                        : "text-slate-500 dark:text-slate-400"
                    }`}
                  >
                    /seat · mo
                  </span>
                </div>
                <p
                  className={`mt-1 h-5 text-xs ${
                    plan.featured
                      ? "text-emerald-300"
                      : "text-emerald-600 dark:text-emerald-400"
                  }`}
                >
                  {isAnnual
                    ? `Save $${(plan.monthly - plan.annual) * 12} per seat each year`
                    : " "}
                </p>

                <a
                  href="#get-started"
                  className={`mt-6 inline-flex w-full items-center justify-center rounded-xl px-5 py-3 text-sm font-semibold outline-none transition-transform duration-200 hover:-translate-y-0.5 focus-visible:ring-2 focus-visible:ring-offset-2 ${
                    plan.featured
                      ? "bg-gradient-to-r from-indigo-500 to-violet-500 text-white shadow-lg shadow-indigo-500/30 hover:shadow-indigo-500/50 focus-visible:ring-indigo-400 focus-visible:ring-offset-slate-900"
                      : "bg-slate-900 text-white hover:bg-slate-800 focus-visible:ring-slate-500 focus-visible:ring-offset-white dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white dark:focus-visible:ring-offset-slate-900"
                  }`}
                >
                  {plan.cta}
                </a>

                <ul className="mt-7 space-y-3.5">
                  {plan.features.map((feature) => (
                    <li
                      key={feature.label}
                      className={`flex items-center gap-3 text-sm ${
                        feature.included
                          ? plan.featured
                            ? "text-slate-200"
                            : "text-slate-700 dark:text-slate-200"
                          : plan.featured
                            ? "text-slate-500 line-through"
                            : "text-slate-400 line-through dark:text-slate-600"
                      }`}
                    >
                      <span
                        className={`flex h-5 w-5 flex-none items-center justify-center rounded-full ${
                          feature.included
                            ? plan.featured
                              ? "bg-indigo-500/20 text-indigo-300"
                              : "bg-emerald-100 text-emerald-600 dark:bg-emerald-500/15 dark:text-emerald-400"
                            : plan.featured
                              ? "bg-slate-800 text-slate-500"
                              : "bg-slate-100 text-slate-400 dark:bg-slate-800 dark:text-slate-600"
                        }`}
                      >
                        {feature.included ? (
                          <CheckIcon className="h-3.5 w-3.5" />
                        ) : (
                          <DashIcon className="h-3.5 w-3.5" />
                        )}
                      </span>
                      {feature.label}
                    </li>
                  ))}
                </ul>
              </motion.div>
            );
          })}
        </div>

        <p className="mt-10 text-center text-sm text-slate-500 dark:text-slate-400">
          Prices in USD. 14-day free trial on every plan — no card required.{" "}
          <a
            href="#compare"
            className="font-semibold text-indigo-600 underline-offset-4 hover:underline dark:text-indigo-400"
          >
            Compare all features
          </a>
        </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

Toggle Annual Pricing

Toggle Annual Pricing

Original

pricing with monthly/annual toggle

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