Comparison Table

Plan comparison with a highlighted column and check / dash cells.

FeaturesStarterProEnterprise
Projects3UnlimitedUnlimited
Team members110Unlimited
Custom domains
Advanced analytics
SSO & SCIM
Audit logs
SupportCommunityEmailDedicated

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/comparison-table.tsx

    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>
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.