Pricing
Pricing section with annual/monthly toggle.
Default Usage
import { Pricing } from '@/components/landing';
<Pricing />
Location: src/components/landing/Pricing.tsx
Features
- Annual/Monthly billing toggle
- Data-driven plan configuration
- "Most Popular" badge support
- Feature list per plan
- Scroll animation
What's Included
Starterbase comes with:
- 50+ UI components - Buttons, cards, tables, dialogs, forms, and more
- 55+ icons - Essential icons for any application
- 15+ landing components - Hero, features, pricing, FAQ, testimonials, etc.
- Full admin panel - User management, analytics, roles & permissions
Customization
Edit the plans array in Pricing.tsx:
const plans: PricingPlan[] = [
{
nameKey: 'free.name',
descriptionKey: 'free.description',
price: { monthly: 0, annual: 0 },
features: ['free.feature1', 'free.feature2', 'free.feature3'],
ctaKey: 'free.cta',
href: '/register',
},
{
nameKey: 'pro.name',
price: { monthly: 29, annual: 24 },
highlighted: true, // Adds "Most Popular" styling
badge: 'pro.badge',
features: ['pro.feature1', 'pro.feature2', 'pro.feature3', 'pro.feature4'],
// feature4 = "Priority email support"
// ...
},
];
Variants
Variant 1: Three Plans (Default)
Standard 3-column pricing with toggle.
// src/components/landing/Pricing.tsx - Default implementation
Best for: Most products, clear tier differentiation
Variant 2: Two Plans (Simple)
Simplified 2-plan pricing for smaller products.
// src/components/landing/PricingSimple.tsx
'use client';
import { useState } from 'react';
import { useTranslations } from 'next-intl';
import Link from 'next/link';
import { useScrollAnimation } from '@/hooks';
import { Button, Card, CardContent } from '@/components/ui';
import { CheckIcon } from '@/components/icons';
export default function PricingSimple() {
const t = useTranslations('landing.pricing');
const [isAnnual, setIsAnnual] = useState(true);
const { elementRef, isVisible } = useScrollAnimation();
const plans = [
{
name: 'Free',
price: { monthly: 0, annual: 0 },
description: 'Perfect for trying out',
features: [
'Up to 3 projects',
'Basic features',
'Community support',
'5GB storage',
],
cta: 'Get Started',
href: '/register',
popular: false,
},
{
name: 'Pro',
price: { monthly: 29, annual: 24 },
description: 'For professionals',
features: [
'Unlimited projects',
'All features',
'Priority email support',
'100GB storage',
'Advanced analytics',
'Custom domain',
],
cta: 'Start Free Trial',
href: '/register?plan=pro',
popular: true,
},
];
return (
<section id="pricing" className="py-20 sm:py-32 bg-white dark:bg-zinc-950">
<div className="container mx-auto px-4">
<div
ref={elementRef as React.RefObject<HTMLDivElement>}
className={`scroll-fade-in ${isVisible ? 'visible' : ''}`}
>
<div className="text-center mb-12">
<h2 className="text-3xl sm:text-4xl font-bold mb-4">
{t('title')}
</h2>
<p className="text-zinc-600 dark:text-zinc-400 text-lg">
{t('subtitle')}
</p>
</div>
{/* Billing toggle */}
<div className="flex items-center justify-center gap-4 mb-12">
<span className={!isAnnual ? 'font-semibold' : 'text-zinc-500'}>
Monthly
</span>
<button
onClick={() => setIsAnnual(!isAnnual)}
className={`relative w-14 h-7 rounded-full transition-colors ${
isAnnual
? 'bg-zinc-900 dark:bg-zinc-100'
: 'bg-zinc-300 dark:bg-zinc-700'
}`}
>
<span
className={`absolute top-1 w-5 h-5 rounded-full bg-white dark:bg-zinc-900 transition-transform ${
isAnnual ? 'translate-x-8' : 'translate-x-1'
}`}
/>
</button>
<span className={isAnnual ? 'font-semibold' : 'text-zinc-500'}>
Annual
<span className="ml-2 text-sm text-green-600">Save 20%</span>
</span>
</div>
{/* Plans */}
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
{plans.map((plan, index) => (
<Card
key={index}
className={`relative ${
plan.popular
? 'ring-2 ring-zinc-900 dark:ring-zinc-100 md:scale-105'
: ''
}`}
>
{plan.popular && (
<div className="absolute -top-4 left-1/2 -translate-x-1/2 px-4 py-1 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 text-sm font-semibold rounded-full">
Most Popular
</div>
)}
<CardContent className="p-8">
<h3 className="text-2xl font-bold mb-2">{plan.name}</h3>
<p className="text-zinc-600 dark:text-zinc-400 mb-6">
{plan.description}
</p>
<div className="mb-8">
<span className="text-5xl font-bold">
${isAnnual ? plan.price.annual : plan.price.monthly}
</span>
<span className="text-zinc-600 dark:text-zinc-400 ml-2">
/month
</span>
{isAnnual && plan.price.annual < plan.price.monthly && (
<div className="text-sm text-green-600 mt-1">
${plan.price.annual * 12}/year
</div>
)}
</div>
<ul className="space-y-4 mb-8">
{plan.features.map((feature, i) => (
<li key={i} className="flex items-start gap-3">
<CheckIcon className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" />
<span>{feature}</span>
</li>
))}
</ul>
<Button
as={Link}
href={plan.href}
color={plan.popular ? 'dark' : undefined}
outline={!plan.popular}
className="w-full"
>
{plan.cta}
</Button>
</CardContent>
</Card>
))}
</div>
{/* FAQ link */}
<p className="text-center mt-12 text-zinc-600 dark:text-zinc-400">
Have questions?{' '}
<Link href="#faq" className="text-zinc-900 dark:text-white underline">
Check our FAQ
</Link>
</p>
</div>
</div>
</section>
);
}
Best for: Simple products, clear differentiation, solopreneurs
Variant 3: Comparison Table
Detailed feature comparison across plans.
// src/components/landing/PricingComparison.tsx
'use client';
import React, { useState } from 'react';
import { useTranslations } from 'next-intl';
import Link from 'next/link';
import { Button } from '@/components/ui';
import { CheckIcon, XMarkIcon } from '@/components/icons';
export default function PricingComparison() {
const [isAnnual, setIsAnnual] = useState(true);
const plans = [
{ name: 'Free', price: { monthly: 0, annual: 0 } },
{ name: 'Pro', price: { monthly: 29, annual: 24 } },
{ name: 'Enterprise', price: { monthly: 99, annual: 79 } },
];
const features = [
{
category: 'Core Features',
items: [
{ name: 'Projects', values: ['3', 'Unlimited', 'Unlimited'] },
{ name: 'Storage', values: ['5GB', '100GB', '1TB'] },
{ name: 'Team members', values: ['1', '5', 'Unlimited'] },
],
},
{
category: 'Advanced Features',
items: [
{ name: 'Priority email support', values: [false, true, true] },
{ name: 'Custom domain', values: [false, true, true] },
{ name: 'SSO', values: [false, false, true] },
{ name: 'API access', values: [false, true, true] },
],
},
];
return (
<section id="pricing" className="py-20 sm:py-32">
<div className="container mx-auto px-4">
<div className="text-center mb-12">
<h2 className="text-3xl sm:text-4xl font-bold mb-4">
Compare Plans
</h2>
<p className="text-zinc-600 dark:text-zinc-400 text-lg">
Choose the perfect plan for your needs
</p>
</div>
{/* Billing toggle */}
<div className="flex items-center justify-center gap-4 mb-12">
<span className={!isAnnual ? 'font-semibold' : 'text-zinc-500'}>
Monthly
</span>
<button
onClick={() => setIsAnnual(!isAnnual)}
className={`relative w-14 h-7 rounded-full transition-colors ${
isAnnual
? 'bg-zinc-900 dark:bg-zinc-100'
: 'bg-zinc-300 dark:bg-zinc-700'
}`}
>
<span
className={`absolute top-1 w-5 h-5 rounded-full bg-white dark:bg-zinc-900 transition-transform ${
isAnnual ? 'translate-x-8' : 'translate-x-1'
}`}
/>
</button>
<span className={isAnnual ? 'font-semibold' : 'text-zinc-500'}>
Annual <span className="text-green-600">Save 20%</span>
</span>
</div>
{/* Comparison table */}
<div className="max-w-6xl mx-auto overflow-x-auto">
<table className="w-full border-collapse">
{/* Header with pricing */}
<thead>
<tr>
<th className="text-left p-4 border-b border-zinc-200 dark:border-zinc-800">
Features
</th>
{plans.map((plan, index) => (
<th
key={index}
className="p-4 border-b border-zinc-200 dark:border-zinc-800"
>
<div className="text-center">
<div className="font-bold text-lg mb-2">{plan.name}</div>
<div className="text-3xl font-bold mb-4">
$
{isAnnual ? plan.price.annual : plan.price.monthly}
<span className="text-sm font-normal text-zinc-600 dark:text-zinc-400">
/mo
</span>
</div>
<Button
as={Link}
href={`/register?plan=${plan.name.toLowerCase()}`}
color={index === 1 ? 'dark' : undefined}
outline={index !== 1}
className="w-full"
>
Get Started
</Button>
</div>
</th>
))}
</tr>
</thead>
{/* Feature rows */}
<tbody>
{features.map((category, catIndex) => (
<React.Fragment key={catIndex}>
<tr>
<td
colSpan={plans.length + 1}
className="p-4 bg-zinc-50 dark:bg-zinc-900/50 font-semibold border-y border-zinc-200 dark:border-zinc-800"
>
{category.category}
</td>
</tr>
{category.items.map((item, itemIndex) => (
<tr
key={itemIndex}
className="border-b border-zinc-200 dark:border-zinc-800"
>
<td className="p-4 text-zinc-700 dark:text-zinc-300">
{item.name}
</td>
{item.values.map((value, valueIndex) => (
<td key={valueIndex} className="p-4 text-center">
{typeof value === 'boolean' ? (
value ? (
<CheckIcon className="w-5 h-5 text-green-600 mx-auto" />
) : (
<XMarkIcon className="w-5 h-5 text-zinc-300 dark:text-zinc-700 mx-auto" />
)
) : (
<span className="text-zinc-900 dark:text-white font-medium">
{value}
</span>
)}
</td>
))}
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
</div>
</section>
);
}
Best for: Complex products, B2B applications, when features need detailed comparison
Choosing a Variant
| Variant | Use When |
|---|---|
| Three Plans | Standard apps with free, pro, enterprise tiers |
| Two Plans (Simple) | Simple product, clear free vs paid distinction |
| Comparison Table | Many features to compare, enterprise sales focus |