Comparison Table
Plan comparison with a highlighted column and check / dash cells.
| Features | Starter | Pro | Enterprise |
|---|---|---|---|
| Projects | 3 | Unlimited | Unlimited |
| Team members | 1 | 10 | Unlimited |
| Custom domains | |||
| Advanced analytics | |||
| SSO & SCIM | |||
| Audit logs | |||
| Support | Community | Dedicated |
components/ui/comparison-table.tsx
"use client";
import { motion } from "motion/react";
import { Check, Minus } from "lucide-react";
type Row = { feature: string; values: (boolean | string)[] };
const defaultPlans = ["Starter", "Pro", "Enterprise"];
const defaultRows: Row[] = [
{ feature: "Projects", values: ["3", "Unlimited", "Unlimited"] },
{ feature: "Team members", values: ["1", "10", "Unlimited"] },
{ feature: "Custom domains", values: [false, true, true] },
{ feature: "Advanced analytics", values: [false, true, true] },
{ feature: "SSO & SCIM", values: [false, false, true] },
{ feature: "Audit logs", values: [false, false, true] },
{ feature: "Support", values: ["Community", "Email", "Dedicated"] },
];
export function ComparisonTable({ plans = defaultPlans, rows = defaultRows, highlight = 1 }: { plans?: string[]; rows?: Row[]; highlight?: number }) {
return (
<div className="overflow-x-auto rounded-2xl border border-zinc-200 bg-white">
<table className="w-full min-w-[520px] text-sm">
<thead>
<tr>
<th className="p-4 text-left font-medium text-zinc-500">Features</th>
{plans.map((p, i) => (
<th key={p} className={`p-4 text-center font-semibold ${i === highlight ? "bg-blue-50 text-blue-700" : "text-zinc-900"}`}>
{p}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, r) => (
<motion.tr
key={row.feature}
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: r * 0.05 }}
className="border-t border-zinc-100 transition-colors hover:bg-zinc-50/70"
>
<td className="p-4 text-zinc-700">{row.feature}</td>
{row.values.map((v, i) => (
<td key={i} className={`p-4 text-center ${i === highlight ? "bg-blue-50/50" : ""}`}>
{v === true ? (
<Check className="mx-auto size-4 text-blue-600" aria-label="Included" />
) : v === false ? (
<Minus className="mx-auto size-4 text-zinc-300" aria-label="Not included" />
) : (
<span className="text-zinc-700">{v}</span>
)}
</td>
))}
</motion.tr>
))}
</tbody>
</table>
</div>
);
}
export default function Demo() {
return (
<div className="w-full max-w-3xl">
<ComparisonTable />
</div>
);
}
Installation
Install the dependencies
$ npm i motion lucide-reactCopy the source into
components/ui/comparison-table.tsxcomponents/ui/comparison-table.tsx"use client"; import { motion } from "motion/react"; import { Check, Minus } from "lucide-react"; type Row = { feature: string; values: (boolean | string)[] }; const defaultPlans = ["Starter", "Pro", "Enterprise"]; const defaultRows: Row[] = [ { feature: "Projects", values: ["3", "Unlimited", "Unlimited"] }, { feature: "Team members", values: ["1", "10", "Unlimited"] }, { feature: "Custom domains", values: [false, true, true] }, { feature: "Advanced analytics", values: [false, true, true] }, { feature: "SSO & SCIM", values: [false, false, true] }, { feature: "Audit logs", values: [false, false, true] }, { feature: "Support", values: ["Community", "Email", "Dedicated"] }, ]; export function ComparisonTable({ plans = defaultPlans, rows = defaultRows, highlight = 1 }: { plans?: string[]; rows?: Row[]; highlight?: number }) { return ( <div className="overflow-x-auto rounded-2xl border border-zinc-200 bg-white"> <table className="w-full min-w-[520px] text-sm"> <thead> <tr> <th className="p-4 text-left font-medium text-zinc-500">Features</th> {plans.map((p, i) => ( <th key={p} className={`p-4 text-center font-semibold ${i === highlight ? "bg-blue-50 text-blue-700" : "text-zinc-900"}`}> {p} </th> ))} </tr> </thead> <tbody> {rows.map((row, r) => ( <motion.tr key={row.feature} initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} viewport={{ once: true }} transition={{ delay: r * 0.05 }} className="border-t border-zinc-100 transition-colors hover:bg-zinc-50/70" > <td className="p-4 text-zinc-700">{row.feature}</td> {row.values.map((v, i) => ( <td key={i} className={`p-4 text-center ${i === highlight ? "bg-blue-50/50" : ""}`}> {v === true ? ( <Check className="mx-auto size-4 text-blue-600" aria-label="Included" /> ) : v === false ? ( <Minus className="mx-auto size-4 text-zinc-300" aria-label="Not included" /> ) : ( <span className="text-zinc-700">{v}</span> )} </td> ))} </motion.tr> ))} </tbody> </table> </div> ); } export default function Demo() { return ( <div className="w-full max-w-3xl"> <ComparisonTable /> </div> ); }Use it
The file exports the component and a
Demoshowing typical usage. Import whichever you need.