Pricing 2
Compare-by-feature pricing table with sticky tier header (tier name, price, CTA) and ~8 feature rows below.
Preview
Installation
npx shadcn@latest add https://hirael.com/r/pricing-02.jsonCode
components/blocks/pricing-02.tsx
"use client";
import * as React from "react";
import { Check, Minus } from "lucide-react";
import { Button } from "@/registry/hirael/ui/button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/registry/hirael/ui/table";
import { cn } from "@/lib/utils";
type Cell = boolean | string;
type Row = {
feature: string;
hobby: Cell;
pro: Cell;
team: Cell;
};
type Tier = {
key: "hobby" | "pro" | "team";
name: string;
price: string;
cta: string;
ctaVariant: "default" | "outline";
featured?: boolean;
};
const TIERS: readonly Tier[] = [
{
key: "hobby",
name: "Hobby",
price: "$0",
cta: "Start free",
ctaVariant: "outline",
},
{
key: "pro",
name: "Pro",
price: "$18",
cta: "Start trial",
ctaVariant: "default",
featured: true,
},
{
key: "team",
name: "Team",
price: "$48",
cta: "Contact",
ctaVariant: "outline",
},
];
const ROWS: readonly Row[] = [
{
feature: "Registry components",
hobby: "Up to 5",
pro: "Unlimited",
team: "Unlimited",
},
{ feature: "Theme presets", hobby: "3", pro: "Unlimited", team: "Unlimited" },
{ feature: "Private registry mirror", hobby: false, pro: true, team: true },
{ feature: "Shared workspace", hobby: false, pro: false, team: true },
{ feature: "SSO & audit log", hobby: false, pro: false, team: true },
{ feature: "Priority response", hobby: false, pro: true, team: true },
{ feature: "SLA support", hobby: false, pro: false, team: true },
{ feature: "Full source ownership", hobby: true, pro: true, team: true },
];
function CellContent({ value }: { value: Cell }) {
if (value === true) {
return <Check className="size-4 text-foreground" aria-label="Included" />;
}
if (value === false) {
return (
<Minus
className="size-4 text-muted-foreground/50"
aria-label="Not included"
/>
);
}
return (
<span className="font-mono text-xs tabular-nums text-foreground">
{value}
</span>
);
}
export default function Pricing02() {
return (
<section className="bg-background py-20 sm:py-28">
<div className="mx-auto w-full max-w-5xl px-6 md:px-10">
<div className="flex flex-col gap-5">
<span className="font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground">
compare plans
</span>
<h2 className="max-w-2xl font-serif text-4xl font-medium leading-[1.04] tracking-tight sm:text-5xl">
Every feature, side by side.
</h2>
<p className="max-w-2xl text-base text-muted-foreground sm:text-lg">
One table, no marketing fog. See what each tier ships before you
spend a dollar.
</p>
</div>
<div className="mt-12 overflow-hidden rounded-md border border-border bg-card">
<Table className="border-collapse text-start">
<TableHeader className="bg-card">
<TableRow className="border-b border-border hover:bg-transparent">
<TableHead className="w-2/5 px-5 py-5 align-bottom text-start">
<span className="font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground">
plan
</span>
</TableHead>
{TIERS.map((t) => (
<TableHead
key={t.key}
className={cn(
"px-5 py-5 align-bottom text-start",
t.featured && "bg-background/40",
)}
>
<div className="flex flex-col gap-3">
<div className="flex items-baseline justify-between gap-2">
<span className="text-base font-semibold tracking-[-0.01em] text-foreground">
{t.name}
</span>
<span className="font-mono text-xs tabular-nums text-muted-foreground">
{t.price}
<span className="text-muted-foreground">/mo</span>
</span>
</div>
<Button
asChild
variant={t.ctaVariant}
size="sm"
className="w-full"
>
<a href="#">{t.cta}</a>
</Button>
</div>
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{ROWS.map((r) => (
<TableRow
key={r.feature}
className="border-b border-border last:border-b-0 hover:bg-transparent"
>
<TableCell className="px-5 py-4 font-mono text-xs uppercase tracking-[0.08em] text-foreground">
{r.feature}
</TableCell>
{TIERS.map((t) => (
<TableCell
key={t.key}
className={cn(
"px-5 py-4",
t.featured && "bg-background/40",
)}
>
<CellContent value={r[t.key]} />
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</section>
);
}
Dependencies
shadcn registry
buttontable
npm
lucide-react