Plan Feature Comparison Table
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.
npx shadcn@latest add https://webinnoventix.com/r/pricing-comparison-table.jsonexport default function PricingComparisonTable() {
const plans = [
{ name: "Starter", price: "£0", cadence: "free forever", featured: false },
{ name: "Growth", price: "£29", cadence: "per month", featured: true },
{ name: "Enterprise", price: "Custom", cadence: "talk to us", featured: false },
];
const groups = [
{
name: "Core",
rows: [
{ label: "Active projects", values: ["3", "Unlimited", "Unlimited"] },
{ label: "Team members", values: ["1", "10", "Unlimited"] },
{ label: "Storage", values: ["5 GB", "250 GB", "5 TB"] },
],
},
{
name: "Collaboration",
rows: [
{ label: "Shared workspaces", values: [true, true, true] },
{ label: "Real-time editing", values: [false, true, true] },
{ label: "Guest access", values: [false, true, true] },
],
},
{
name: "Security and support",
rows: [
{ label: "Two-factor authentication", values: [true, true, true] },
{ label: "Single sign-on", values: [false, false, true] },
{ label: "Audit logs", values: [false, true, true] },
{ label: "Priority support", values: [false, true, true] },
],
},
];
const renderValue = (value: string | boolean) => {
if (typeof value === "string") {
return <span className="text-sm font-medium text-zinc-900 dark:text-white">{value}</span>;
}
if (value) {
return (
<>
<svg
className="mx-auto h-5 w-5 text-emerald-600 dark:text-emerald-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 className="sr-only">Included</span>
</>
);
}
return (
<>
<svg
className="mx-auto h-4 w-4 text-zinc-300 dark:text-zinc-600"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path d="M4 9h12a1 1 0 1 1 0 2H4a1 1 0 1 1 0-2Z" />
</svg>
<span className="sr-only">Not included</span>
</>
);
};
return (
<section className="bg-white px-6 py-16 dark:bg-zinc-950 md:py-24">
<div className="mx-auto max-w-5xl">
<div className="mb-10 text-center">
<h2 className="text-3xl font-bold tracking-tight text-zinc-900 sm:text-4xl dark:text-white">
Compare every plan
</h2>
<p className="mt-3 text-zinc-600 dark:text-zinc-400">
See exactly what is included so you can pick the plan that fits.
</p>
</div>
<div className="overflow-x-auto rounded-2xl border border-zinc-200 dark:border-zinc-800">
<table className="w-full min-w-[40rem] border-collapse text-left">
<caption className="sr-only">
Feature comparison across the Starter, Growth and Enterprise plans
</caption>
<thead>
<tr>
<th
scope="col"
className="w-1/3 bg-zinc-50 p-5 align-bottom dark:bg-zinc-900/60"
>
<span className="text-sm font-medium text-zinc-500 dark:text-zinc-400">
Plans
</span>
</th>
{plans.map((plan) => (
<th
key={plan.name}
scope="col"
className={
"p-5 align-bottom " +
(plan.featured
? "bg-indigo-50 dark:bg-indigo-500/10"
: "bg-zinc-50 dark:bg-zinc-900/60")
}
>
<div className="flex flex-col gap-2">
<span className="flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-white">
{plan.name}
{plan.featured && (
<span className="rounded-full bg-indigo-600 px-2 py-0.5 text-xs font-semibold text-white">
Popular
</span>
)}
</span>
<span>
<span className="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
{plan.price}
</span>{" "}
<span className="text-xs font-normal text-zinc-500 dark:text-zinc-400">
{plan.cadence}
</span>
</span>
<a
href="#"
className={
"mt-1 rounded-lg px-3 py-2 text-center text-xs font-semibold transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 " +
(plan.featured
? "bg-indigo-600 text-white hover:bg-indigo-500"
: "bg-zinc-900 text-white hover:bg-zinc-700 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-200")
}
>
Choose {plan.name}
</a>
</div>
</th>
))}
</tr>
</thead>
{groups.map((group) => (
<tbody key={group.name}>
<tr>
<th
scope="colgroup"
colSpan={4}
className="border-t border-zinc-200 bg-white px-5 pb-2 pt-6 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400"
>
{group.name}
</th>
</tr>
{group.rows.map((row) => (
<tr key={row.label} className="border-t border-zinc-100 dark:border-zinc-800/70">
<th
scope="row"
className="p-5 text-sm font-normal text-zinc-700 dark:text-zinc-300"
>
{row.label}
</th>
{row.values.map((value, index) => (
<td
key={plans[index].name}
className={
"p-5 text-center " +
(plans[index].featured ? "bg-indigo-50/50 dark:bg-indigo-500/5" : "")
}
>
{renderValue(value)}
</td>
))}
</tr>
))}
</tbody>
))}
</table>
</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 quoteMore blocks
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.

