All Taste components
Pricing
Plan comparison table
Every plan's features in one table, grouped by topic, for people who want the detail.
3 plans · grouped rows · ticks, dashes and values · highlighted column
Compare plans
| Free$0 | Pro$12/mo | Studio$29/mo | |
|---|---|---|---|
| Invoicing | |||
| Invoices a month | 5 | Unlimited | Unlimited |
| Recurring invoices | Not included | Included | Included |
| 24 currencies | Included | Included | Included |
| Getting paid | |||
| Card and bank payments | Included | Included | Included |
| Custom reminder schedule | Not included | Included | Included |
| Automatic late fees | Not included | Included | Included |
| Team | |||
| Seats | 1 | 1 | 5 |
| Approval before sending | Not included | Not included | Included |
| Client portal | Not included | Not included | Included |
import { Check, Minus } from "lucide-react";
import { cn } from "@/lib/utils";
const plans = [
{ name: "Free", price: "$0" },
{ name: "Pro", price: "$12/mo", highlight: true },
{ name: "Studio", price: "$29/mo" },
];
// true = included, false = not included, string = a value
type Cell = boolean | string;
const groups: { name: string; rows: { feature: string; values: [Cell, Cell, Cell] }[] }[] = [
{ name: "Invoicing", rows: [
{ feature: "Invoices a month", values: ["5", "Unlimited", "Unlimited"] },
{ feature: "Recurring invoices", values: [false, true, true] },
{ feature: "24 currencies", values: [true, true, true] },
] },
{ name: "Getting paid", rows: [
{ feature: "Card and bank payments", values: [true, true, true] },
{ feature: "Custom reminder schedule", values: [false, true, true] },
{ feature: "Automatic late fees", values: [false, true, true] },
] },
{ name: "Team", rows: [
{ feature: "Seats", values: ["1", "1", "5"] },
{ feature: "Approval before sending", values: [false, false, true] },
{ feature: "Client portal", values: [false, false, true] },
] },
];
function Value({ value }: { value: Cell }) {
if (value === true) return <><Check className="mx-auto size-4 text-primary" aria-hidden /><span className="sr-only">Included</span></>;
if (value === false) return <><Minus className="mx-auto size-4 text-fg-subtle" aria-hidden /><span className="sr-only">Not included</span></>;
return <span className="text-fg">{value}</span>;
}
export function PlanComparison() {
return (
<section aria-labelledby="compare-title" className="bg-bg font-sans">
<div className="mx-auto max-w-4xl px-4 py-20 sm:px-6">
<h2 id="compare-title" className="text-center text-3xl font-semibold tracking-tight text-fg">Compare plans</h2>
<div className="mt-10 overflow-x-auto">
<table className="w-full min-w-[480px] table-fixed border-collapse text-sm">
<thead>
<tr>
<td className="w-2/5" />
{plans.map((p) => (
<th key={p.name} scope="col" className={cn("px-2 pb-4 pt-3 text-center align-bottom font-semibold text-fg", p.highlight && "rounded-t-lg bg-primary-soft text-primary-soft-fg")}>
{p.name}
<span className="block text-xs font-normal text-fg-subtle">{p.price}</span>
</th>
))}
</tr>
</thead>
{groups.map((g) => (
<tbody key={g.name}>
<tr>
<th scope="colgroup" colSpan={4} className="border-b border-border-strong pb-2 pt-6 text-left text-xs font-medium text-fg">{g.name}</th>
</tr>
{g.rows.map((r) => (
<tr key={r.feature}>
<th scope="row" className="border-b border-border py-3 pr-2 text-left font-normal text-fg-muted">{r.feature}</th>
{r.values.map((v, i) => (
<td key={i} className={cn("border-b border-border px-2 py-3 text-center", plans[i].highlight && "bg-primary-soft/40")}>
<Value value={v} />
</td>
))}
</tr>
))}
</tbody>
))}
</table>
</div>
</div>
</section>
);
}
The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.