Dependencies
tailwindcssshadcn/uilucide-react
shadcn/ui components
npx shadcn@latest add cardnpx shadcn@latest add badgenpx shadcn@latest add buttonnpx shadcn@latest add switch

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

ComponentPurpose
Card, CardHeader, CardContent, CardFootershadcn/ui card structure
BadgePlan label, "Popular" highlight
ButtonCTA — use variant="outline" for secondary plans
SwitchBilling period toggle
scale-105Visually elevate the recommended plan
ring-2 ring-red-500Alternative highlight for featured card

1. Basic Pricing Cards

Three-tier pricing layout with feature lists and CTA buttons.

tsx
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.

tsx
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.

tsx
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.

tsx
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.

tsx
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

Use shadcn/ui's Card component as the base, add a Badge for the plan label, list features with a check icon from lucide-react, and use a Button for the CTA. Wrap multiple cards in a flex or grid container. Add a ring or scale transform on the recommended plan to make it stand out.
Use a useState boolean for billing period (monthly/annual) and shadcn/ui's Switch component to toggle it. Multiply prices by a discount factor when annual is active and display the savings percentage as a badge next to the toggle.

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.

Related Components