Comparison Table Pricing
Original · freepricing comparison table
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-comparison-table.json"use client";
import { Fragment, useId, useState } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
type Billing = "monthly" | "annual";
type FeatureValue = boolean | string;
interface Plan {
id: string;
name: string;
tagline: string;
monthly: number;
annual: number;
cta: string;
popular?: boolean;
}
interface Feature {
label: string;
values: [FeatureValue, FeatureValue, FeatureValue];
}
interface FeatureGroup {
name: string;
features: Feature[];
}
const PLANS: Plan[] = [
{
id: "starter",
name: "Starter",
tagline: "For solo builders finding their footing.",
monthly: 12,
annual: 9,
cta: "Start free trial",
},
{
id: "growth",
name: "Growth",
tagline: "For teams shipping every single week.",
monthly: 29,
annual: 23,
cta: "Start free trial",
popular: true,
},
{
id: "scale",
name: "Scale",
tagline: "For orgs that need real control.",
monthly: 79,
annual: 63,
cta: "Talk to sales",
},
];
const GROUPS: FeatureGroup[] = [
{
name: "Core workspace",
features: [
{ label: "Active projects", values: ["3", "Unlimited", "Unlimited"] },
{ label: "Team members", values: ["2", "15", "Unlimited"] },
{ label: "File storage", values: ["10 GB", "250 GB", "2 TB"] },
{ label: "Version history", values: ["7 days", "90 days", "Unlimited"] },
],
},
{
name: "Collaboration",
features: [
{ label: "Real-time editing", values: [true, true, true] },
{ label: "Comments & @mentions", values: [true, true, true] },
{ label: "Guest & client access", values: [false, true, true] },
{ label: "Roles & permissions", values: [false, true, true] },
],
},
{
name: "Automation & AI",
features: [
{ label: "Automation runs / mo", values: ["500", "10,000", "Unlimited"] },
{ label: "AI drafting assist", values: [false, true, true] },
{ label: "Custom workflow builder", values: [false, false, true] },
],
},
{
name: "Security & support",
features: [
{ label: "SSO / SAML sign-in", values: [false, false, true] },
{ label: "Audit logs", values: [false, false, true] },
{ label: "Priority support", values: [false, true, true] },
{ label: "99.9% uptime SLA", values: [false, false, true] },
],
},
];
const POPULAR_INDEX = PLANS.findIndex((p) => p.popular);
function CheckIcon() {
return (
<svg
viewBox="0 0 20 20"
fill="none"
aria-hidden="true"
className="h-3.5 w-3.5"
>
<path
d="M4.5 10.5l3.2 3.2 7.8-8.4"
stroke="currentColor"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function DashIcon() {
return (
<svg
viewBox="0 0 20 20"
fill="none"
aria-hidden="true"
className="h-3.5 w-3.5"
>
<path
d="M5 10h10"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
);
}
function SparkIcon() {
return (
<svg
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
className="h-3.5 w-3.5"
>
<path d="M10 1.5l1.7 4.6 4.8 1.7-4.8 1.7L10 14.1 8.3 9.5 3.5 7.8l4.8-1.7L10 1.5z" />
</svg>
);
}
function ShieldIcon() {
return (
<svg
viewBox="0 0 20 20"
fill="none"
aria-hidden="true"
className="h-4 w-4"
>
<path
d="M10 2.2l6 2.2v4.2c0 3.7-2.5 6.7-6 8-3.5-1.3-6-4.3-6-8V4.4l6-2.2z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M7.4 10.1l1.9 1.9 3.5-3.9"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function ValueCell({ value }: { value: FeatureValue }) {
if (value === true) {
return (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400">
<CheckIcon />
<span className="sr-only">Included</span>
</span>
);
}
if (value === false) {
return (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-slate-100 text-slate-400 dark:bg-zinc-800 dark:text-zinc-600">
<DashIcon />
<span className="sr-only">Not included</span>
</span>
);
}
return (
<span className="text-sm font-medium text-slate-700 dark:text-zinc-200">
{value}
</span>
);
}
export default function PricexComparisonTable() {
const reduce = useReducedMotion();
const [billing, setBilling] = useState<Billing>("annual");
const headingId = useId();
const colClass = (i: number) =>
i === POPULAR_INDEX
? "bg-indigo-50/70 dark:bg-indigo-500/10"
: "";
const priceAnim = reduce
? {}
: {
initial: { opacity: 0, y: 8 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -8 },
transition: { duration: 0.22, ease: "easeOut" as const },
};
return (
<section
aria-labelledby={headingId}
className="relative w-full overflow-hidden bg-white px-4 py-20 text-slate-900 sm:px-6 sm:py-28 dark:bg-zinc-950 dark:text-zinc-50"
>
<style>{`
@keyframes pricex-fade-up {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pricex-shimmer {
0% { background-position: -140% 0; }
100% { background-position: 240% 0; }
}
@keyframes pricex-glow {
0%, 100% { opacity: 0.35; }
50% { opacity: 0.7; }
}
.pricex-badge-shimmer {
background-image: linear-gradient(
110deg,
transparent 20%,
rgba(255,255,255,0.55) 50%,
transparent 80%
);
background-size: 200% 100%;
animation: pricex-shimmer 3.4s linear infinite;
}
.pricex-orb { animation: pricex-glow 6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.pricex-badge-shimmer,
.pricex-orb { animation: none !important; }
}
`}</style>
{/* ambient background */}
<div
aria-hidden="true"
className="pricex-orb pointer-events-none absolute -top-24 left-1/2 h-72 w-72 -translate-x-1/2 rounded-full bg-indigo-400/20 blur-3xl dark:bg-indigo-500/20"
/>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 bottom-0 h-px bg-gradient-to-r from-transparent via-slate-200 to-transparent dark:via-zinc-800"
/>
<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-1.5 rounded-full border border-indigo-200 bg-indigo-50 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-indigo-700 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300">
<SparkIcon />
Pricing
</span>
<h2
id={headingId}
className="mt-5 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white"
>
Pricing that scales with your team
</h2>
<p className="mt-4 text-base leading-relaxed text-slate-600 dark:text-zinc-400">
Compare every plan side by side. Switch tiers or cancel anytime —
no hidden fees, no surprise overage bills, no forced annual lock-in.
</p>
</div>
{/* billing toggle */}
<div className="mt-8 flex items-center justify-center">
<div
role="group"
aria-label="Billing period"
className="inline-flex items-center rounded-full border border-slate-200 bg-slate-100 p-1 dark:border-zinc-800 dark:bg-zinc-900"
>
{(["monthly", "annual"] as const).map((mode) => {
const active = billing === mode;
return (
<button
key={mode}
type="button"
aria-pressed={active}
onClick={() => setBilling(mode)}
className="relative rounded-full px-4 py-1.5 text-sm font-semibold transition-colors focus-visible:outline-none 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-zinc-900"
>
{active ? (
<motion.span
layoutId={reduce ? undefined : "pricex-billing"}
className="absolute inset-0 rounded-full bg-white shadow-sm dark:bg-zinc-700"
transition={{ type: "spring", stiffness: 420, damping: 34 }}
/>
) : null}
<span
className={
"relative z-10 flex items-center gap-1.5 " +
(active
? "text-slate-900 dark:text-white"
: "text-slate-500 dark:text-zinc-400")
}
>
{mode === "monthly" ? "Monthly" : "Annual"}
{mode === "annual" ? (
<span className="rounded-full bg-emerald-100 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400">
Save 20%
</span>
) : null}
</span>
</button>
);
})}
</div>
</div>
{/* table */}
<motion.div
initial={reduce ? false : { opacity: 0, y: 24 }}
whileInView={reduce ? undefined : { opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.12 }}
transition={{ duration: 0.5, ease: "easeOut" }}
className="mt-12 overflow-x-auto rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-950"
>
<table className="w-full min-w-[760px] border-collapse text-left">
<caption className="sr-only">
Feature comparison across Starter, Growth and Scale plans
</caption>
<thead>
<tr>
<th
scope="col"
className="sticky left-0 z-30 w-[30%] bg-white p-5 align-bottom dark:bg-zinc-950"
>
<span className="text-sm font-semibold text-slate-500 dark:text-zinc-400">
Compare features
</span>
</th>
{PLANS.map((plan, i) => {
const price = billing === "annual" ? plan.annual : plan.monthly;
return (
<th
key={plan.id}
scope="col"
className={
"relative border-l border-slate-100 p-5 align-bottom dark:border-zinc-800/80 " +
colClass(i)
}
>
{plan.popular ? (
<span className="absolute -top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-full bg-indigo-600 px-2.5 py-1 text-[10px] font-bold uppercase tracking-wide text-white shadow-sm">
<span className="pricex-badge-shimmer absolute inset-0" />
<span className="relative flex items-center gap-1">
<SparkIcon />
Most popular
</span>
</span>
) : null}
<div className="text-base font-bold text-slate-900 dark:text-white">
{plan.name}
</div>
<p className="mt-1 min-h-[2.5rem] text-xs leading-snug text-slate-500 dark:text-zinc-400">
{plan.tagline}
</p>
<div className="mt-3 flex items-baseline gap-1">
<span className="text-sm font-medium text-slate-500 dark:text-zinc-400">
$
</span>
<AnimatePresence mode="wait" initial={false}>
<motion.span
key={`${plan.id}-${billing}`}
{...priceAnim}
className="text-3xl font-bold tabular-nums text-slate-900 dark:text-white"
>
{price}
</motion.span>
</AnimatePresence>
<span className="text-sm font-medium text-slate-500 dark:text-zinc-400">
/mo
</span>
</div>
<p className="mt-1 text-xs text-slate-400 dark:text-zinc-500">
{billing === "annual"
? `Billed $${plan.annual * 12}/yr`
: "Billed monthly"}
</p>
<a
href="#get-started"
className={
"mt-4 flex w-full items-center justify-center rounded-lg px-4 py-2 text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-zinc-950 " +
(plan.popular
? "bg-indigo-600 text-white hover:bg-indigo-500"
: "border border-slate-300 text-slate-800 hover:border-slate-400 hover:bg-slate-50 dark:border-zinc-700 dark:text-zinc-100 dark:hover:border-zinc-600 dark:hover:bg-zinc-900")
}
>
{plan.cta}
</a>
</th>
);
})}
</tr>
</thead>
<tbody>
{GROUPS.map((group) => (
<Fragment key={group.name}>
<tr>
<th
scope="colgroup"
colSpan={4}
className="sticky left-0 z-20 bg-slate-50 px-5 py-2.5 text-xs font-bold uppercase tracking-wide text-slate-500 dark:bg-zinc-900 dark:text-zinc-400"
>
{group.name}
</th>
</tr>
{group.features.map((feature) => (
<tr
key={`${group.name}-${feature.label}`}
className="group border-t border-slate-100 dark:border-zinc-800/70"
>
<th
scope="row"
className="sticky left-0 z-10 bg-white p-5 text-sm font-medium text-slate-700 group-hover:bg-slate-50 dark:bg-zinc-950 dark:text-zinc-200 dark:group-hover:bg-zinc-900/60"
>
{feature.label}
</th>
{feature.values.map((value, i) => (
<td
key={i}
className={
"border-l border-slate-100 p-5 text-center dark:border-zinc-800/70 " +
colClass(i)
}
>
<ValueCell value={value} />
</td>
))}
</tr>
))}
</Fragment>
))}
</tbody>
</table>
</motion.div>
{/* footer note */}
<div className="mt-6 flex flex-col items-center justify-center gap-2 text-sm text-slate-500 sm:flex-row sm:gap-6 dark:text-zinc-400">
<span className="inline-flex items-center gap-1.5">
<span className="text-emerald-600 dark:text-emerald-400">
<ShieldIcon />
</span>
30-day money-back guarantee
</span>
<span className="inline-flex items-center gap-1.5">
<span className="text-indigo-600 dark:text-indigo-400">
<CheckIcon />
</span>
No credit card required to trial
</span>
<span className="hidden text-slate-300 sm:inline dark:text-zinc-700">
•
</span>
<span>All prices in USD</span>
</div>
</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

Toggle Annual Pricing
Originalpricing with monthly/annual toggle

Single Highlight Pricing
Originalsingle highlighted plan

Cards Gradient Pricing
Originalgradient pricing cards

Minimal Pricing
Originalminimal pricing tiers

