React pricing components built with shadcn/ui and Tailwind CSS. Five patterns covering the most common pricing UI needs — from a basic three-plan layout to a full billing toggle and feature comparison table.
Key Classes Reference
| Component | Purpose |
|---|---|
Card, CardHeader, CardContent, CardFooter | shadcn/ui card structure |
Badge | Plan label, "Popular" highlight |
Button | CTA — use variant="outline" for secondary plans |
Switch | Billing period toggle |
scale-105 | Visually elevate the recommended plan |
ring-2 ring-red-500 | Alternative highlight for featured card |
1. Basic Pricing Cards
Three-tier pricing layout with feature lists and CTA buttons.
import { Check } from "lucide-react"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
const plans = [
{
name: "Starter",
price: 9,
badge: null,
description: "Perfect for side projects",
features: ["5 components", "Community support", "1 project", "Basic analytics"],
cta: "Get Started",
variant: "outline" as const,
},
{
name: "Pro",
price: 29,
badge: "Most Popular",
description: "For professional developers",
features: ["Unlimited components", "Priority support", "10 projects", "Advanced analytics", "Custom domain"],
cta: "Start Free Trial",
variant: "default" as const,
},
{
name: "Team",
price: 79,
badge: null,
description: "For growing teams",
features: ["Everything in Pro", "Team collaboration", "Unlimited projects", "SSO", "SLA guarantee"],
cta: "Contact Sales",
variant: "outline" as const,
},
]
export default function BasicPricing() {
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 p-6">
{plans.map((plan) => (
<Card
key={plan.name}
className={`relative flex flex-col ${plan.badge ? "border-2 border-red-500 shadow-lg scale-105" : ""}`}
>
{plan.badge && (
<Badge className="absolute -top-3 left-1/2 -translate-x-1/2 bg-red-500 text-white">
{plan.badge}
</Badge>
)}
<CardHeader>
<CardTitle className="text-lg">{plan.name}</CardTitle>
<p className="text-sm text-muted-foreground">{plan.description}</p>
<div className="mt-2">
<span className="text-4xl font-bold">${plan.price}</span>
<span className="text-muted-foreground">/mo</span>
</div>
</CardHeader>
<CardContent className="flex-1">
<ul className="space-y-2">
{plan.features.map((f) => (
<li key={f} className="flex items-center gap-2 text-sm">
<Check className="h-4 w-4 text-green-500 shrink-0" />
{f}
</li>
))}
</ul>
</CardContent>
<CardFooter>
<Button
variant={plan.variant}
className={`w-full ${plan.badge ? "bg-red-500 hover:bg-red-600 text-white border-none" : ""}`}
>
{plan.cta}
</Button>
</CardFooter>
</Card>
))}
</div>
)
}2. Monthly / Annual Toggle
Billing period switch that updates prices and shows savings.
import { useState } from "react"
import { Check } from "lucide-react"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch"
import { Label } from "@/components/ui/label"
const plans = [
{ name: "Starter", monthly: 9, features: ["5 components", "1 project", "Community support"] },
{ name: "Pro", monthly: 29, features: ["Unlimited components", "10 projects", "Priority support"] },
{ name: "Team", monthly: 79, features: ["Everything in Pro", "Unlimited projects", "SSO"] },
]
export default function BillingToggle() {
const [annual, setAnnual] = useState(false)
return (
<div className="p-6">
<div className="flex items-center justify-center gap-3 mb-8">
<Label htmlFor="billing">Monthly</Label>
<Switch id="billing" checked={annual} onCheckedChange={setAnnual} />
<Label htmlFor="billing" className="flex items-center gap-2">
Annual
<Badge className="bg-green-500 text-white text-xs">Save 20%</Badge>
</Label>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{plans.map((plan) => {
const price = annual ? Math.round(plan.monthly * 0.8) : plan.monthly
return (
<Card key={plan.name} className="flex flex-col">
<CardHeader>
<CardTitle>{plan.name}</CardTitle>
<div className="mt-2 flex items-end gap-1">
<span className="text-4xl font-bold">${price}</span>
<span className="text-muted-foreground mb-1">/mo</span>
{annual && (
<span className="text-xs text-muted-foreground mb-1 line-through ml-1">
${plan.monthly}
</span>
)}
</div>
{annual && (
<p className="text-xs text-green-600">
Billed annually — save ${(plan.monthly - price) * 12}/yr
</p>
)}
</CardHeader>
<CardContent className="flex-1">
<ul className="space-y-2">
{plan.features.map((f) => (
<li key={f} className="flex items-center gap-2 text-sm">
<Check className="h-4 w-4 text-green-500 shrink-0" />
{f}
</li>
))}
</ul>
</CardContent>
<CardFooter>
<Button className="w-full bg-red-500 hover:bg-red-600 text-white">
Get {plan.name}
</Button>
</CardFooter>
</Card>
)
})}
</div>
</div>
)
}3. Dark Pricing Card
Dark themed pricing card for dark admin dashboards.
import { Check, Zap } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
export default function DarkPricing() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 p-6 bg-gray-950 min-h-screen">
{/* Free Card */}
<div className="rounded-2xl border border-gray-800 bg-gray-900 p-8 flex flex-col">
<div className="mb-6">
<h3 className="text-white font-bold text-xl mb-1">Free</h3>
<p className="text-gray-400 text-sm">Start building for free</p>
<div className="mt-4">
<span className="text-5xl font-bold text-white">$0</span>
<span className="text-gray-400">/mo</span>
</div>
</div>
<ul className="space-y-3 flex-1 mb-8">
{["3 projects", "Basic components", "Community support"].map((f) => (
<li key={f} className="flex items-center gap-3 text-gray-300 text-sm">
<Check className="h-4 w-4 text-gray-500" />
{f}
</li>
))}
</ul>
<button className="w-full rounded-xl border border-gray-700 text-white py-2.5 text-sm font-medium hover:bg-gray-800 transition-colors">
Get Started Free
</button>
</div>
{/* Pro Card */}
<div className="rounded-2xl border border-red-500/50 bg-gradient-to-b from-red-950/40 to-gray-900 p-8 flex flex-col relative overflow-hidden">
<div className="absolute top-0 right-0 w-40 h-40 bg-red-500/10 rounded-full -translate-y-20 translate-x-20" />
<div className="mb-6 relative">
<div className="flex items-center gap-2 mb-1">
<h3 className="text-white font-bold text-xl">Pro</h3>
<Badge className="bg-red-500 text-white text-xs">
<Zap className="h-3 w-3 mr-1" /> Popular
</Badge>
</div>
<p className="text-gray-400 text-sm">For serious developers</p>
<div className="mt-4">
<span className="text-5xl font-bold text-white">$29</span>
<span className="text-gray-400">/mo</span>
</div>
</div>
<ul className="space-y-3 flex-1 mb-8 relative">
{["Unlimited projects", "All components", "Priority support", "Custom domain", "Analytics"].map((f) => (
<li key={f} className="flex items-center gap-3 text-gray-200 text-sm">
<Check className="h-4 w-4 text-red-400" />
{f}
</li>
))}
</ul>
<button className="w-full rounded-xl bg-red-500 hover:bg-red-600 text-white py-2.5 text-sm font-semibold transition-colors relative">
Start Free Trial
</button>
</div>
</div>
)
}4. Comparison Table
Feature comparison table across plans with check and cross indicators.
import { Check, X } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
const features = [
{ name: "Projects", starter: "3", pro: "10", team: "Unlimited" },
{ name: "Components", starter: "50", pro: "Unlimited", team: "Unlimited" },
{ name: "Analytics", starter: false, pro: true, team: true },
{ name: "Priority support",starter: false, pro: true, team: true },
{ name: "Custom domain", starter: false, pro: true, team: true },
{ name: "SSO", starter: false, pro: false, team: true },
{ name: "SLA", starter: false, pro: false, team: true },
]
function Cell({ value }: { value: string | boolean }) {
if (typeof value === "boolean") {
return value
? <Check className="h-5 w-5 text-green-500 mx-auto" />
: <X className="h-5 w-5 text-gray-300 mx-auto" />
}
return <span className="text-sm font-medium">{value}</span>
}
export default function ComparisonTable() {
return (
<div className="p-6 overflow-x-auto">
<table className="w-full">
<thead>
<tr>
<th className="text-left py-3 text-sm text-muted-foreground font-medium w-1/3">Feature</th>
{[
{ name: "Starter", price: "$9/mo", highlight: false },
{ name: "Pro", price: "$29/mo", highlight: true },
{ name: "Team", price: "$79/mo", highlight: false },
].map((p) => (
<th key={p.name} className="text-center py-3">
<div className="flex flex-col items-center gap-1">
<span className={`text-sm font-bold ${p.highlight ? "text-red-500" : ""}`}>
{p.name}
</span>
<span className="text-xs text-muted-foreground">{p.price}</span>
{p.highlight && <Badge className="bg-red-500 text-white text-xs">Popular</Badge>}
</div>
</th>
))}
</tr>
</thead>
<tbody>
{features.map((f, i) => (
<tr key={f.name} className={i % 2 === 0 ? "bg-gray-50" : ""}>
<td className="py-3 px-2 text-sm text-gray-700">{f.name}</td>
<td className="py-3 text-center"><Cell value={f.starter} /></td>
<td className="py-3 text-center"><Cell value={f.pro} /></td>
<td className="py-3 text-center"><Cell value={f.team} /></td>
</tr>
))}
</tbody>
</table>
<div className="grid grid-cols-3 gap-4 mt-6">
{["Starter", "Pro", "Team"].map((name) => (
<Button
key={name}
variant={name === "Pro" ? "default" : "outline"}
className={`w-full ${name === "Pro" ? "bg-red-500 hover:bg-red-600 text-white" : ""}`}
>
Get {name}
</Button>
))}
</div>
</div>
)
}5. Minimal Single Pricing Card
Clean single pricing card with gradient border and feature highlights.
import { Check, ArrowRight } from "lucide-react"
export default function MinimalPricingCard() {
return (
<div className="p-6 flex justify-center">
<div className="w-full max-w-sm rounded-2xl p-px bg-gradient-to-b from-red-500 to-blue-600 shadow-xl">
<div className="rounded-2xl bg-white p-8">
<p className="text-xs font-semibold uppercase tracking-widest text-red-500 mb-1">
Pro Plan
</p>
<h2 className="text-3xl font-bold text-gray-900 mb-1">
$29
<span className="text-base font-normal text-gray-400">/month</span>
</h2>
<p className="text-sm text-gray-500 mb-6">
Everything you need to ship faster.
Cancel anytime.
</p>
<ul className="space-y-3 mb-8">
{[
"Unlimited components",
"Priority support",
"10 projects",
"Custom domain",
"Advanced analytics",
].map((f) => (
<li key={f} className="flex items-center gap-3 text-sm text-gray-700">
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-red-50">
<Check className="h-3 w-3 text-red-500" />
</span>
{f}
</li>
))}
</ul>
<button className="w-full flex items-center justify-center gap-2 rounded-xl bg-gray-900 hover:bg-gray-800 text-white py-3 text-sm font-semibold transition-colors">
Start Free Trial
<ArrowRight className="h-4 w-4" />
</button>
<p className="text-center text-xs text-gray-400 mt-3">
No credit card required
</p>
</div>
</div>
</div>
)
}Frequently Asked Questions
Need a Full React + Next.js Dashboard Template?
Get a complete React + Next.js dashboard with 50+ components — built by the same team behind BootstrapPlanet.
Browse Templates →Use code FIRST30 for 30% off.