Docs
Pricing Table 3-Tier

Pricing Table 3-Tier

A modern, sophisticated 3-tier pricing table with discount badges, coming soon features, and elegant design. Perfect for SaaS and subscription services.

Pricing

Simple, transparent pricing

Choose the perfect plan for your needs. All plans include a 14-day free trial.

Starter

Free

Perfect for individuals and small projects

$0
/mo
Features
Up to 3 projects
Basic analytics
Community support
1 GB storage
API access
Advanced features
Priority support
Custom integrations
Most Popular

Pro

20% OFF

Limited Offer

Best for growing teams and businesses

$23
$29/mo

Save 20%

• $6 off
Features
Unlimited projects
Advanced analytics
Priority support
50 GB storage
API access
Advanced features
Team collaboration
Soon
Custom integrations

Enterprise

Custom

15% OFF

For large organizations with custom needs

$84
$99/mo

Save 15%

• $15 off
Features
Unlimited everything
Advanced analytics
24/7 dedicated support
Unlimited storage
API access
Advanced features
Team collaboration
Custom integrations
Soon

All plans include a 14-day free trial • No credit card required

Features

  • 3-tier layout - Starter, Pro, Enterprise structure
  • Modern design - Sophisticated gradients and refined styling
  • Discount support - Show promotional discounts with custom labels
  • Coming soon badges - Mark features as "Coming Soon" with amber styling
  • Popular badge - Highlight recommended plan with glow effect
  • Billing toggle - Switch between monthly/yearly pricing
  • Savings calculator - Shows yearly savings automatically
  • Feature comparison - Check/X/Clock icons for feature states
  • Responsive design - 3 columns → 1 column on mobile
  • Shadcn components - Built with Card, Badge, Button
  • TypeScript support - Full type safety
  • Production-ready - Copy and paste to use

Installation

Copy and paste the following code into your project.

"use client";
 
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { ArrowRight, Check, Clock, Sparkles, X } from "lucide-react";
 
interface PricingFeature {
  text: string;
  included: boolean;
  comingSoon?: boolean;
}
 
interface PricingTier {
  name: string;
  description: string;
  price: {
    monthly: number;
    yearly: number;
  };
  discount?: {
    percentage: number;
    label?: string;
  };
  badge?: string;
  popular?: boolean;
  features: PricingFeature[];
  cta: {
    text: string;
    href?: string;
    onClick?: () => void;
  };
}
 
interface PricingTable3TierProps {
  badge?: string;
  headline: string;
  description?: string;
  billingPeriod?: "monthly" | "yearly";
  tiers: PricingTier[];
}
 
export function PricingTable3Tier({
  badge,
  headline,
  description,
  billingPeriod = "monthly",
  tiers,
}: PricingTable3TierProps) {
  return (
    <section className="relative overflow-hidden py-24 sm:py-32">
      <div className="absolute inset-0 -z-10 bg-gradient-to-br from-background via-muted/30 to-background" />
      <div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_30%_20%,rgba(120,119,198,0.05),transparent_50%),radial-gradient(circle_at_70%_80%,rgba(120,119,198,0.05),transparent_50%)]" />
      <div className="absolute inset-0 -z-10 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]" />
 
      <div className="container mx-auto px-6">
        <div className="mx-auto mb-20 max-w-3xl text-center">
          {badge && (
            <div className="mb-6 inline-flex animate-in fade-in slide-in-from-bottom-3 duration-700">
              <Badge
                variant="outline"
                className="border-border/40 bg-muted/50 px-3 py-1 text-xs font-medium text-muted-foreground"
              >
                {badge}
              </Badge>
            </div>
          )}
          <h2 className="mb-6 animate-in fade-in slide-in-from-bottom-4 bg-gradient-to-br from-foreground to-foreground/70 bg-clip-text text-4xl font-bold tracking-tight text-transparent duration-1000 sm:text-5xl md:text-6xl">
            {headline}
          </h2>
          {description && (
            <p className="animate-in fade-in slide-in-from-bottom-5 text-lg leading-relaxed text-muted-foreground duration-1000 sm:text-xl">
              {description}
            </p>
          )}
        </div>
 
        <div className="mx-auto grid max-w-7xl gap-8 overflow-visible lg:grid-cols-3">
          {tiers.map((tier, index) => (
            <div
              key={index}
              className="group relative animate-in fade-in slide-in-from-bottom-6 duration-1000"
              style={{ animationDelay: `${index * 100}ms` }}
            >
              {tier.popular && (
                <div className="absolute -top-3 left-1/2 z-10 -translate-x-1/2">
                  <div className="relative">
                    <div className="absolute inset-0 rounded-full bg-primary/20 blur-md" />
                    <Badge className="relative whitespace-nowrap bg-gradient-to-r from-primary via-primary to-primary/90 px-4 py-1.5 text-[11px] font-semibold uppercase tracking-wide text-primary-foreground shadow-lg">
                      Most Popular
                    </Badge>
                  </div>
                </div>
              )}
 
              <Card
                className={`relative h-full overflow-visible transition-all duration-500 ${
                  tier.popular
                    ? "mt-3 scale-[1.02] border-primary/40 bg-gradient-to-b from-card to-card/50 shadow-2xl shadow-primary/10 ring-1 ring-primary/10 lg:scale-105"
                    : "mt-3 border-border/40 bg-card/50 backdrop-blur-sm hover:border-border/60 hover:shadow-xl"
                }`}
              >
                {tier.popular && (
                  <>
                    <div className="absolute inset-0 -z-10 bg-gradient-to-br from-primary/[0.03] via-transparent to-primary/[0.03]" />
                    <div className="absolute -right-20 -top-20 h-40 w-40 rounded-full bg-primary/5 blur-3xl" />
                    <div className="absolute -bottom-20 -left-20 h-40 w-40 rounded-full bg-primary/5 blur-3xl" />
                  </>
                )}
 
                <CardHeader className="space-y-6 pb-8 pt-10">
                  <div className="flex items-start justify-between gap-2">
                    <h3 className="text-2xl font-bold tracking-tight">
                      {tier.name}
                    </h3>
                    <div className="flex flex-col items-end gap-2">
                      {tier.badge && !tier.popular && (
                        <Badge
                          variant="secondary"
                          className="bg-secondary/50 text-xs backdrop-blur-sm"
                        >
                          {tier.badge}
                        </Badge>
                      )}
                      {tier.discount && (
                        <div className="flex flex-col items-end gap-1">
                          <div className="rounded-md bg-green-500/10 px-2.5 py-1 ring-1 ring-green-500/20">
                            <p className="text-sm font-bold text-green-600 dark:text-green-400">
                              {tier.discount.percentage}% OFF
                            </p>
                          </div>
                          {tier.discount.label && (
                            <p className="text-xs font-medium text-green-600 dark:text-green-400">
                              {tier.discount.label}
                            </p>
                          )}
                        </div>
                      )}
                    </div>
                  </div>
 
                  <p className="min-h-[2.5rem] text-sm leading-relaxed text-muted-foreground">
                    {tier.description}
                  </p>
                  <div className="space-y-2">
                    <div className="flex items-baseline gap-2">
                      {tier.discount ? (
                        <>
                          <span className="bg-gradient-to-br from-foreground to-foreground/70 bg-clip-text text-6xl font-bold tracking-tight text-transparent">
                            $
                            {Math.round(
                              (billingPeriod === "monthly"
                                ? tier.price.monthly
                                : tier.price.yearly) *
                                (1 - tier.discount.percentage / 100),
                            )}
                          </span>
                          <div className="flex flex-col gap-0.5">
                            <span className="text-sm font-medium text-muted-foreground line-through">
                              $
                              {billingPeriod === "monthly"
                                ? tier.price.monthly
                                : tier.price.yearly}
                            </span>
                            <span className="text-xs text-muted-foreground">
                              /{billingPeriod === "monthly" ? "mo" : "yr"}
                            </span>
                          </div>
                        </>
                      ) : (
                        <>
                          <span className="bg-gradient-to-br from-foreground to-foreground/70 bg-clip-text text-6xl font-bold tracking-tight text-transparent">
                            $
                            {billingPeriod === "monthly"
                              ? tier.price.monthly
                              : tier.price.yearly}
                          </span>
                          <div className="flex flex-col">
                            <span className="text-sm font-medium text-muted-foreground">
                              /{billingPeriod === "monthly" ? "mo" : "yr"}
                            </span>
                          </div>
                        </>
                      )}
                    </div>
 
                    {tier.discount && (
                      <div className="flex items-center gap-2">
                        <p className="text-sm font-semibold text-green-600 dark:text-green-400">
                          Save {tier.discount.percentage}%
                        </p>
                        <span className="text-xs text-muted-foreground">
                          • $
                          {Math.round(
                            (billingPeriod === "monthly"
                              ? tier.price.monthly
                              : tier.price.yearly) *
                              (tier.discount.percentage / 100),
                          )}{" "}
                          off
                        </span>
                      </div>
                    )}
 
                    {billingPeriod === "yearly" &&
                      tier.price.monthly > 0 &&
                      !tier.discount && (
                        <p className="text-sm font-semibold text-green-600 dark:text-green-400">
                          Save $
                          {(
                            tier.price.monthly * 12 -
                            tier.price.yearly
                          ).toFixed(0)}{" "}
                          per year
                        </p>
                      )}
                  </div>
 
                  <Button
                    onClick={tier.cta.onClick}
                    size="lg"
                    className={`group/btn relative w-full overflow-hidden font-medium transition-all duration-300 ${
                      tier.popular
                        ? "bg-primary text-primary-foreground shadow-lg shadow-primary/25 hover:shadow-xl hover:shadow-primary/30"
                        : "border border-border/50 bg-secondary/50 text-secondary-foreground backdrop-blur-sm hover:bg-secondary/80 hover:shadow-lg"
                    }`}
                  >
                    <span className="relative z-10 flex items-center">
                      {tier.cta.text}
                      <ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover/btn:translate-x-1" />
                    </span>
                    {tier.popular && (
                      <div className="absolute inset-0 -z-0 bg-gradient-to-r from-transparent via-white/10 to-transparent opacity-0 transition-opacity group-hover/btn:opacity-100" />
                    )}
                  </Button>
                </CardHeader>
 
                <CardContent className="space-y-4 pb-10">
                  <div className="relative">
                    <div className="absolute inset-0 flex items-center">
                      <div className="w-full border-t border-border/50" />
                    </div>
                    <div className="relative flex justify-center text-xs uppercase">
                      <span className="bg-card px-2 text-muted-foreground">
                        Features
                      </span>
                    </div>
                  </div>
 
                  <div className="space-y-4 pt-2">
                    {tier.features.map((feature, featureIndex) => (
                      <div
                        key={featureIndex}
                        className="flex items-start gap-3 transition-all hover:translate-x-1"
                      >
                        <div
                          className={`mt-0.5 flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full transition-all ${
                            feature.comingSoon
                              ? "bg-amber-500/10 text-amber-600 ring-1 ring-amber-500/20 dark:text-amber-400"
                              : feature.included
                                ? "bg-primary/15 text-primary ring-1 ring-primary/20"
                                : "bg-muted/50 text-muted-foreground/50"
                          }`}
                        >
                          {feature.comingSoon ? (
                            <Clock className="h-3.5 w-3.5" />
                          ) : feature.included ? (
                            <Check className="h-3.5 w-3.5 stroke-[3]" />
                          ) : (
                            <X className="h-3.5 w-3.5" />
                          )}
                        </div>
                        <span
                          className={`flex items-center gap-2 text-sm leading-relaxed ${
                            feature.comingSoon
                              ? "font-medium text-foreground"
                              : feature.included
                                ? "font-medium text-foreground"
                                : "text-muted-foreground/60 line-through"
                          }`}
                        >
                          {feature.text}
                          {feature.comingSoon && (
                            <Badge
                              variant="outline"
                              className="border-amber-500/30 bg-amber-500/10 px-1.5 py-0 text-[9px] font-medium text-amber-600 dark:text-amber-400"
                            >
                              Soon
                            </Badge>
                          )}
                        </span>
                      </div>
                    ))}
                  </div>
                </CardContent>
              </Card>
            </div>
          ))}
        </div>
 
        <div className="mt-20 animate-in fade-in slide-in-from-bottom-8 text-center duration-1000">
          <p className="text-sm text-muted-foreground">
            All plans include a 14-day free trial • No credit card required
          </p>
        </div>
      </div>
    </section>
  );
}

Update the import paths to match your project setup.

Usage

Basic Usage

import PricingTable3Tier from "@/components/ui/pricing-table-3tier";
 
export default function PricingPage() {
  return (
    <PricingTable3Tier
      headline="Simple pricing"
      tiers={[
        {
          name: "Starter",
          description: "For individuals",
          price: { monthly: 0, yearly: 0 },
          features: [
            { text: "Up to 3 projects", included: true },
            { text: "Basic support", included: true },
            { text: "API access", included: false },
            { text: "Advanced features", included: true, comingSoon: true },
          ],
          cta: { text: "Get Started", onClick: () => {} },
        },
        {
          name: "Pro",
          description: "For teams",
          price: { monthly: 29, yearly: 290 },
          discount: {
            percentage: 20,
            label: "Limited Offer",
          },
          popular: true,
          features: [
            { text: "Unlimited projects", included: true },
            { text: "Priority support", included: true },
            { text: "API access", included: true },
          ],
          cta: { text: "Start Trial", onClick: () => {} },
        },
        {
          name: "Enterprise",
          description: "For organizations",
          price: { monthly: 99, yearly: 990 },
          features: [
            { text: "Unlimited everything", included: true },
            { text: "24/7 support", included: true },
            { text: "API access", included: true },
          ],
          cta: { text: "Contact Sales", onClick: () => {} },
        },
      ]}
    />
  );
}

With All Features

<PricingTable3Tier
  badge="Pricing"
  headline="Simple, transparent pricing"
  description="Choose the perfect plan for your needs. All plans include a 14-day free trial."
  billingPeriod="monthly"
  tiers={[
    {
      name: "Starter",
      description: "Perfect for individuals and small projects",
      price: { monthly: 0, yearly: 0 },
      badge: "Free",
      features: [
        { text: "Up to 3 projects", included: true },
        { text: "Basic analytics", included: true },
        { text: "Community support", included: true },
        { text: "API access", included: false },
      ],
      cta: {
        text: "Get Started",
        onClick: () => console.log("Starter"),
      },
    },
    // ... more tiers
  ]}
/>

With Router Navigation

"use client";
 
import { useRouter } from "next/navigation";
import PricingTable3Tier from "@/components/ui/pricing-table-3tier";
 
export default function PricingPage() {
  const router = useRouter();
 
  return (
    <PricingTable3Tier
      headline="Choose your plan"
      tiers={[
        {
          name: "Pro",
          description: "For growing teams",
          price: { monthly: 29, yearly: 290 },
          popular: true,
          features: [/* ... */],
          cta: {
            text: "Start Free Trial",
            onClick: () => router.push("/signup?plan=pro"),
          },
        },
      ]}
    />
  );
}

Props

PricingTable3TierProps

PropTypeDefaultDescription
badgestringundefinedOptional badge text above headline
headlinestringRequiredMain section headline
descriptionstringundefinedOptional description below headline
billingPeriod"monthly" | "yearly""monthly"Current billing period
tiersPricingTier[]RequiredArray of pricing tier objects

PricingTier Object

PropTypeDescription
namestringTier name (e.g., "Starter", "Pro")
descriptionstringShort description of the tier
priceobjectPrice object with monthly and yearly
price.monthlynumberMonthly price
price.yearlynumberYearly price
discountobjectOptional discount configuration
discount.percentagenumberDiscount percentage (e.g., 20 for 20%)
discount.labelstringOptional custom label (e.g., "Limited Offer")
badgestringOptional badge (e.g., "Free", "Custom")
popularbooleanMark as popular/recommended
featuresPricingFeature[]Array of feature objects
ctaobjectCall-to-action button
cta.textstringButton text
cta.hrefstringOptional link URL
cta.onClick() => voidOptional click handler

PricingFeature Object

PropTypeDescription
textstringFeature description
includedbooleanWhether feature is included
comingSoonbooleanOptional - Mark feature as coming soon

TypeScript Interface

interface PricingFeature {
  text: string;
  included: boolean;
  comingSoon?: boolean;
}
 
interface PricingTier {
  name: string;
  description: string;
  price: {
    monthly: number;
    yearly: number;
  };
  discount?: {
    percentage: number;
    label?: string;
  };
  badge?: string;
  popular?: boolean;
  features: PricingFeature[];
  cta: {
    text: string;
    href?: string;
    onClick?: () => void;
  };
}
 
interface PricingTable3TierProps {
  badge?: string;
  headline: string;
  description?: string;
  billingPeriod?: "monthly" | "yearly";
  tiers: PricingTier[];
}

Advanced Features

Discount Badges

Add promotional discounts to any tier:

{
  name: "Pro",
  price: { monthly: 29, yearly: 290 },
  discount: {
    percentage: 20,
    label: "Limited Offer", // Optional custom label
  },
  // ... other props
}

Without a custom label, it shows "Save 20%":

discount: {
  percentage: 15, // Shows "Save 15%"
}

Coming Soon Features

Mark features that are in development:

features: [
  { text: "Team collaboration", included: true, comingSoon: true },
  { text: "API access", included: true },
  { text: "Custom integrations", included: false },
]

Features with comingSoon: true display:

  • Clock icon (amber color)
  • "Soon" badge next to the feature text
  • No strikethrough styling

Customization

Add Billing Toggle

Create a toggle to switch between monthly and yearly:

"use client";
 
import { useState } from "react";
import { Switch } from "@/components/ui/switch";
 
export default function PricingPage() {
  const [billingPeriod, setBillingPeriod] = useState<"monthly" | "yearly">("monthly");
 
  return (
    <div>
      <div className="mb-8 flex items-center justify-center gap-4">
        <span className={billingPeriod === "monthly" ? "font-semibold" : ""}>
          Monthly
        </span>
        <Switch
          checked={billingPeriod === "yearly"}
          onCheckedChange={(checked) =>
            setBillingPeriod(checked ? "yearly" : "monthly")
          }
        />
        <span className={billingPeriod === "yearly" ? "font-semibold" : ""}>
          Yearly
        </span>
        <Badge>Save 20%</Badge>
      </div>
 
      <PricingTable3Tier
        billingPeriod={billingPeriod}
        tiers={[/* ... */]}
      />
    </div>
  );
}
{tier.popular && (
  <div className="absolute -top-4 left-1/2 -translate-x-1/2">
    <Badge className="bg-gradient-to-r from-primary to-purple-600 px-4 py-1">
      Most Popular
    </Badge>
  </div>
)}

Customize Feature Icons

The component uses three icon states:

// Included feature (green check)
<Check className="h-3.5 w-3.5 stroke-[3]" />
 
// Coming soon feature (amber clock)
<Clock className="h-3.5 w-3.5" />
 
// Not included feature (muted X)
<X className="h-3.5 w-3.5" />

Change Discount Badge Style

{tier.discount && (
  <div className="inline-flex items-center gap-1.5 rounded-full bg-gradient-to-r from-green-600 to-green-500 px-3 py-1">
    <Sparkles className="h-3 w-3" />
    {tier.discount.label || `Save ${tier.discount.percentage}%`}
  </div>
)}

Use Cases

Perfect for:

  • SaaS pricing pages
  • Subscription services
  • Software products
  • API pricing
  • Service tiers
  • Membership plans
  • Tool subscriptions
  • Platform pricing

Best Practices

  1. Keep it simple - 3 tiers is optimal for decision-making
  2. Highlight one plan - Mark your recommended tier as popular
  3. Clear features - Use simple, benefit-focused language
  4. Consistent features - Show same features across all tiers
  5. Yearly discount - Offer 15-20% discount for annual billing
  6. Free tier - Consider offering a free starter plan
  7. Clear CTAs - Use action-oriented button text