Pricing
7 blocksPricing 01
Preview
npx nateui@latest add PricingPlansToggleDark
import { useState } from 'react';
import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import Segment from '@/components/ui/Segment';
import Tag from '@/components/ui/Tag';
import IconFrame from '@/components/composites/IconFrame';
import { PiCheckCircleFill, PiCrownFill, PiCubeFill, PiLightningFill } from 'react-icons/pi';
type BillingCycle = 'monthly' | 'yearly';
type Tier = {
name: string;
description: string;
monthlyPrice: number;
features: string[];
popular?: boolean;
};
const tierIcon = {
Basic: PiCubeFill,
Pro: PiLightningFill,
Premium: PiCrownFill,
} as const;
const tiers: Tier[] = [
{
name: 'Basic',
description: 'For individuals just getting started.',
monthlyPrice: 12,
features: [
'3 active projects',
'10 GB storage',
'Email support',
'Standard exports',
'1 team member',
],
},
{
name: 'Pro',
description: 'For growing teams that need more power.',
monthlyPrice: 39,
popular: true,
features: [
'Unlimited projects',
'100 GB storage',
'Priority support',
'Advanced exports',
'Up to 10 team members',
],
},
{
name: 'Premium',
description: 'For organizations that need it all.',
monthlyPrice: 89,
features: [
'Unlimited projects',
'1 TB storage',
'Dedicated support',
'Advanced exports and API access',
'Unlimited team members',
],
},
];
export default function PricingPlansToggle() {
const [cycle, setCycle] = useState<BillingCycle>('monthly');
const yearly = cycle === 'yearly';
return (
<section aria-labelledby="pricing-plans-toggle-title" className="w-full">
<div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div>
<h3
id="pricing-plans-toggle-title"
className="text-2xl font-semibold"
>
Plans that grow with your team
</h3>
<p className="mt-1 max-w-md text-muted-foreground">
Simple, transparent pricing that scales as your team grows.
</p>
</div>
<Segment
value={cycle}
onChange={(value) => setCycle(value as BillingCycle)}
aria-label="Billing cycle"
>
<Segment.Item value="monthly">Monthly</Segment.Item>
<Segment.Item value="yearly">Yearly</Segment.Item>
</Segment>
</div>
<div className="mt-8 grid grid-cols-1 gap-4 md:grid-cols-3">
{tiers.map((tier) => {
const price = yearly ? Math.round((tier.monthlyPrice * 10) / 12) : tier.monthlyPrice;
const Icon = tierIcon[tier.name as keyof typeof tierIcon];
return (
<Card key={tier.name}>
<div className="flex items-start justify-between gap-2">
<IconFrame variant="layered">
<Icon
aria-hidden="true"
className={tier.popular ? 'text-lg text-primary' : 'text-lg'}
/>
</IconFrame>
{tier.popular && (
<Tag className="border-0 bg-primary-soft text-primary">Popular</Tag>
)}
</div>
<h5 className="mt-4 text-lg font-semibold text-card-foreground">{tier.name}</h5>
<p className="text-muted-foreground">{tier.description}</p>
<div className="mt-4 flex items-baseline gap-1">
<span className="text-2xl font-semibold text-card-foreground tabular-nums">
${price}
</span>
<span className="text-muted-foreground">/mo</span>
</div>
<p className="text-xs text-muted-foreground">
{yearly ? 'Billed annually' : 'Billed monthly'}
</p>
<div className="mt-4 border-t pt-4">
<div className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Features
</div>
<ul className="mt-4 space-y-2">
{tier.features.map((feature) => (
<li key={feature} className="flex items-start gap-2">
<PiCheckCircleFill aria-hidden="true" className="mt-0.5 shrink-0 text-lg text-success" />
<span className="min-w-0">{feature}</span>
</li>
))}
</ul>
</div>
<Button type="button" block variant={tier.popular ? 'solid' : 'default'} className="mt-4">
Get started
</Button>
</Card>
);
})}
</div>
</section>
);
}
Pricing 02
Preview
npx nateui@latest add PricingPlanComparisonTableDark
import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import classNames from '@/utils/classNames';
import { PiCheck } from 'react-icons/pi';
type Plan = {
name: string;
headline: string;
description: string;
cta: string;
featured?: boolean;
outlined?: boolean;
inactive?: boolean;
};
const plans: Plan[] = [
{
name: 'Basic',
headline: 'Free',
description: 'The essentials for a growing team: track and manage your everyday work.',
cta: 'Current plan',
inactive: true,
},
{
name: 'Pro',
headline: '$24/mo',
description: 'Best for a small team running active projects: move faster and stay in sync.',
cta: 'Upgrade to Pro',
featured: true,
},
{
name: 'Custom',
headline: 'Custom rate',
description: 'Pick this plan if you need a custom solution for your business.',
cta: 'Talk to us',
outlined: true,
},
];
type Cell =
| { kind: 'check' }
| { kind: 'note'; note: string }
| { kind: 'text'; value: string }
| { kind: 'empty' };
type FeatureRow = {
label: string;
basic: Cell;
pro: Cell;
};
type FeatureGroup = {
name: string;
rows: FeatureRow[];
};
const check: Cell = { kind: 'check' };
const empty: Cell = { kind: 'empty' };
const quote: Cell = { kind: 'text', value: 'Contact us for a quote' };
const featureGroups: FeatureGroup[] = [
{
name: 'Core features',
rows: [
{ label: 'Team dashboard', basic: check, pro: check },
{
label: 'Advanced analytics',
basic: { kind: 'note', note: 'Verify your workspace to enable this' },
pro: { kind: 'note', note: 'Verify your workspace to enable this' },
},
{ label: 'Scheduled reports', basic: empty, pro: empty },
],
},
{
name: 'Workspace',
rows: [
{ label: 'Shared file storage', basic: check, pro: check },
{
label: 'Storage overage',
basic: { kind: 'text', value: '$0.40 per GB' },
pro: { kind: 'text', value: 'No charge' },
},
{
label: 'Transfer fee (includes bandwidth and processing charges)',
basic: { kind: 'text', value: '8% + $0.20' },
pro: { kind: 'text', value: '6.5% + $0.20' },
},
],
},
{
name: 'Support',
rows: [
{ label: 'Community support', basic: check, pro: check },
{ label: 'Priority support', basic: empty, pro: check },
],
},
{
name: 'Automation',
rows: [
{ label: 'Workflow builder', basic: quote, pro: quote },
{ label: 'Custom API access (REST, GraphQL)', basic: quote, pro: quote },
],
},
];
function CellContent({ cell }: { cell: Cell }) {
if (cell.kind === 'check') {
return <PiCheck aria-hidden="true" className="text-lg text-primary" />;
}
if (cell.kind === 'note') {
return (
<div className="flex items-start gap-2">
<PiCheck aria-hidden="true" className="mt-0.5 shrink-0 text-lg text-primary" />
<span className="text-xs italic leading-snug">{cell.note}</span>
</div>
);
}
if (cell.kind === 'text') {
return <span>{cell.value}</span>;
}
return null;
}
export default function PricingPlanComparisonTable() {
const [firstGroup, ...restGroups] = featureGroups;
return (
<section aria-labelledby="pricing-plan-comparison-title" className="w-full">
<h4 id="pricing-plan-comparison-title" className="text-lg font-semibold text-foreground">
Choose a subscription plan
</h4>
<p className="mt-1 text-muted-foreground">
Select a plan that fits how your team works today.
</p>
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-3">
{plans.map((plan) => (
<Card
key={plan.name}
bodyClass="flex h-full flex-col p-4 text-center"
className={plan.featured ? 'border-primary bg-primary' : undefined}
>
<div className={plan.featured ? 'text-primary-foreground/80' : 'text-muted-foreground'}>
{plan.name}
</div>
<div
className={classNames(
'mt-2 text-2xl font-bold',
plan.featured ? 'text-primary-foreground' : 'text-card-foreground',
)}
>
{plan.headline}
</div>
<p
className={classNames(
'mt-4 flex-1',
plan.featured ? 'text-primary-foreground/80' : 'text-muted-foreground',
)}
>
{plan.description}
</p>
<Button
type="button"
block
disabled={plan.inactive}
variant={plan.featured ? 'solid' : 'default'}
className={classNames(
'mt-8',
plan.featured && 'bg-white text-primary hover:bg-white/90',
)}
>
{plan.cta}
</Button>
</Card>
))}
</div>
<h5 className="mt-8 text-lg font-semibold text-foreground">Compare features</h5>
<div className="mt-4 overflow-hidden rounded-card border bg-card">
<div className="overflow-x-auto">
<div
role="table"
aria-label="Plan feature comparison"
className="grid min-w-2xl grid-cols-3"
>
<div role="row" className="contents">
<div role="columnheader" className="flex items-end px-4 py-4">
<span className="text-base font-semibold text-primary">{firstGroup.name}</span>
</div>
<div role="columnheader" className="bg-primary/5 px-4 py-4">
<div className="text-muted-foreground">Basic</div>
<div className="text-base font-semibold text-foreground">Free</div>
</div>
<div role="columnheader" className="px-4 py-4">
<div className="text-muted-foreground">Pro</div>
<div className="text-base font-semibold text-foreground">$24 USD/mo</div>
</div>
</div>
{firstGroup.rows.map((row) => (
<div key={row.label} role="row" className="contents">
<div role="rowheader" className="border-t p-4 text-foreground">
{row.label}
</div>
<div role="cell" className="border-t bg-primary/5 p-4">
<CellContent cell={row.basic} />
</div>
<div role="cell" className="border-t p-4">
<CellContent cell={row.pro} />
</div>
</div>
))}
{restGroups.flatMap((group) => [
<div key={group.name} role="row" className="contents">
<div role="rowheader" className="border-t px-4 py-4 text-base font-semibold text-primary">
{group.name}
</div>
<div role="cell" className="border-t bg-primary/5" />
<div role="cell" className="border-t" />
</div>,
...group.rows.map((row) => (
<div key={row.label} role="row" className="contents">
<div role="rowheader" className="border-t p-4 text-foreground">
{row.label}
</div>
<div role="cell" className="border-t bg-primary/5 p-4">
<CellContent cell={row.basic} />
</div>
<div role="cell" className="border-t p-4">
<CellContent cell={row.pro} />
</div>
</div>
)),
])}
</div>
</div>
</div>
</section>
);
}
Pricing 03
Preview
npx nateui@latest add PricingPlanMatrixTableDark
import { Fragment } from 'react';
import Button from '@/components/ui/Button';
import Slider from '@/components/ui/Slider';
import Tag from '@/components/ui/Tag';
import Tooltip from '@/components/ui/Tooltip';
import Container from '@/components/composites/Container';
import classNames from '@/utils/classNames';
import { PiCheck, PiInfo, PiMinus } from 'react-icons/pi';
const seatMarks = [
{ value: 1 , label: '1 User' },
{ value: 50 },
{ value: 100 },
{ value: 150 },
{ value: 200 },
{ value: 250 },
{ value: 300, label: '300 Users' },
];
type Plan = {
name: string;
popular?: boolean;
price: string;
description: string;
};
const plans: Plan[] = [
{
name: 'Starter',
popular: true,
price: '$19',
description: 'For small teams that need the essentials, nothing more.',
},
{
name: 'Team',
price: '$49',
description: 'For growing teams that need automation and deeper reporting.',
},
{
name: 'Enterprise',
price: '$99',
description: 'For organizations that need enterprise grade control.',
},
];
type Cell =
| { kind: 'check' }
| { kind: 'dash' }
| { kind: 'text'; value: string };
type FeatureRow = {
label: string;
tooltip: string;
starter: Cell;
team: Cell;
enterprise: Cell;
};
type FeatureGroup = {
name: string;
rows: FeatureRow[];
};
const check: Cell = { kind: 'check' };
const dash: Cell = { kind: 'dash' };
const featureGroups: FeatureGroup[] = [
{
name: 'Core plan',
rows: [
{
label: 'Team workspace',
tooltip: 'A shared workspace for your whole team.',
starter: check,
team: check,
enterprise: check,
},
{
label: 'Seats',
tooltip: 'Team members who can sign in and collaborate.',
starter: { kind: 'text', value: '5' },
team: { kind: 'text', value: '20' },
enterprise: { kind: 'text', value: 'Unlimited' },
},
{
label: 'Storage',
tooltip: 'Combined file storage across your workspace.',
starter: { kind: 'text', value: '20 GB' },
team: { kind: 'text', value: '100 GB' },
enterprise: { kind: 'text', value: 'Unlimited' },
},
{
label: 'Standard support',
tooltip: 'Email support with same-day response.',
starter: check,
team: check,
enterprise: check,
},
{
label: 'Workflow automation',
tooltip: 'Trigger actions automatically from workspace events.',
starter: dash,
team: check,
enterprise: check,
},
{
label: 'Connected apps',
tooltip: 'Link third-party tools to your workspace.',
starter: dash,
team: check,
enterprise: check,
},
],
},
{
name: 'Insights',
rows: [
{
label: 'Reporting',
tooltip: 'Depth of the metrics available on your dashboard.',
starter: { kind: 'text', value: 'Basic' },
team: { kind: 'text', value: 'Advanced' },
enterprise: { kind: 'text', value: 'Advanced' },
},
{
label: 'Export data',
tooltip: 'Download records as CSV or PDF.',
starter: check,
team: check,
enterprise: check,
},
{
label: 'Scheduled digests',
tooltip: 'Automatic report delivery by email.',
starter: check,
team: check,
enterprise: check,
},
{
label: 'API access',
tooltip: 'Read and write workspace data programmatically.',
starter: dash,
team: check,
enterprise: check,
},
{
label: 'Custom dashboards',
tooltip: 'Build your own saved report views.',
starter: dash,
team: check,
enterprise: check,
},
{
label: 'Custom fields',
tooltip: "Add fields specific to how your team works.",
starter: dash,
team: dash,
enterprise: check,
},
],
},
{
name: 'Security & access',
rows: [
{
label: 'Single sign-on',
tooltip: 'Sign in with your existing identity provider.',
starter: check,
team: check,
enterprise: check,
},
{
label: 'Role-based permissions',
tooltip: 'Restrict access by role across the workspace.',
starter: dash,
team: check,
enterprise: check,
},
{
label: 'Audit log',
tooltip: 'A record of every change made in your workspace.',
starter: dash,
team: dash,
enterprise: check,
},
{
label: 'Data retention history',
tooltip: 'Recover past versions of your workspace data.',
starter: dash,
team: dash,
enterprise: check,
},
],
},
];
function CellContent({ cell }: { cell: Cell }) {
if (cell.kind === 'check') {
return (
<>
<PiCheck aria-hidden="true" className="text-lg text-success" />
<span className="sr-only">Included</span>
</>
);
}
if (cell.kind === 'dash') {
return (
<>
<PiMinus aria-hidden="true" className="text-lg" />
<span className="sr-only">Not included</span>
</>
);
}
return <span>{cell.value}</span>;
}
export default function PricingPlanMatrixTable() {
return (
<section aria-labelledby="pricing-plan-matrix-title" className="w-full">
<Container>
<div>
<h3 id="pricing-plan-matrix-title" className="text-2xl font-semibold text-foreground">
Plans for every team size
</h3>
<p className="mt-1 text-muted-foreground">
Straightforward pricing with every feature included, no matter your team size.
</p>
<div className="mt-8 mb-4 px-8">
<Slider
defaultValue={100}
min={0}
max={300}
step={50}
marks={seatMarks}
showTooltipOnHover
tooltip={(value) => `${value} Users`}
thumbAriaLabel="Number of users"
/>
</div>
</div>
<div className="mt-8 overflow-x-auto">
<table className="w-full min-w-3xl table-fixed border-collapse">
<colgroup>
<col className="w-1/4" />
<col className="w-1/4" />
<col className="w-1/4" />
<col className="w-1/4" />
</colgroup>
<thead>
<tr className="border-b">
<th scope="col">
<span className="sr-only">Feature</span>
</th>
{plans.map((plan) => (
<th key={plan.name} scope="col" className="px-4 py-4 text-left align-bottom">
<span className="inline-flex items-center gap-2 text-lg font-semibold text-foreground">
{plan.name}
{plan.popular && (
<Tag className="border-0 bg-primary-soft text-xs text-primary">Popular</Tag>
)}
</span>
</th>
))}
</tr>
</thead>
<tbody>
<tr>
<th scope="row">
<span className="sr-only">Choose your plan</span>
</th>
{plans.map((plan) => (
<td key={plan.name} className="px-4 py-8 align-top">
<div className="flex flex-col gap-4 justify-between">
<div>
<div className="flex items-baseline gap-1">
<span className="text-2xl font-semibold text-foreground">{plan.price}</span>
<span>/ month</span>
</div>
<p className="mt-2 text-sm text-muted-foreground">{plan.description}</p>
</div>
<div className="mt-4 flex flex-col gap-2">
<Button type="button" block variant={plan.popular ? 'solid' : 'default'}>
Get started
</Button>
</div>
</div>
</td>
))}
</tr>
{featureGroups.map((group, groupIndex) => (
<Fragment key={group.name}>
<tr>
<th
scope="colgroup"
colSpan={4}
className={classNames(
'px-4 pb-4 text-left text-base font-semibold text-primary',
groupIndex === 0 ? 'pt-0' : 'pt-8',
)}
>
{group.name}
</th>
</tr>
{group.rows.map((row, rowIndex) => (
<tr
key={row.label}
className={classNames(
rowIndex === group.rows.length - 1 ? '' : 'border-b',
)}
>
<th scope="row" className="px-4 py-4 text-left text-sm font-medium text-foreground">
<span className="inline-flex items-center gap-1.5">
{row.label}
<Tooltip title={row.tooltip} tabIndex={0} aria-label={`About ${row.label}`}>
<PiInfo aria-hidden="true" className="text-base" />
</Tooltip>
</span>
</th>
{[row.starter, row.team, row.enterprise].map((cell, cellIndex) => (
<td key={cellIndex} className="px-4 py-4">
<div className="flex items-center justify-center gap-1.5 text-center">
<CellContent cell={cell} />
</div>
</td>
))}
</tr>
))}
</Fragment>
))}
</tbody>
<tfoot>
<tr>
<th scope="row">
<span className="sr-only">Choose your plan</span>
</th>
{plans.map((plan) => (
<td key={plan.name} className="px-4 pt-8 pb-8">
<div className="flex flex-col gap-2">
<Button type="button" block variant={plan.popular ? 'solid' : 'default'}>
Get started
</Button>
</div>
</td>
))}
</tr>
</tfoot>
</table>
</div>
</Container>
</section>
);
}
Pricing 04
Preview
npx nateui@latest add PricingSliderPlanCardDark
import { useState } from 'react';
import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import Slider from '@/components/ui/Slider';
import Tag from '@/components/ui/Tag';
import Tooltip from '@/components/ui/Tooltip';
import { PiCheckCircleFill, PiInfo } from 'react-icons/pi';
type Tier = {
pillLabel: string;
name: string;
price: number | null;
description: string;
live: string;
staging: string;
cta: string;
};
const tiers: Tier[] = [
{
pillLabel: '1 Website',
name: 'Solo',
price: 8,
description: 'For a single personal project.',
live: '1 Live Website',
staging: '1 Staging Website',
cta: 'Buy Now',
},
{
pillLabel: '5 Websites',
name: 'Starter',
price: 19,
description: 'For a small handful of client sites.',
live: '5 Live Websites',
staging: '5 Staging Websites',
cta: 'Buy Now',
},
{
pillLabel: '10 Websites',
name: 'Studio',
price: 34,
description: 'For a growing freelance practice.',
live: '10 Live Websites',
staging: '10 Staging Websites',
cta: 'Buy Now',
},
{
pillLabel: '20 Websites',
name: 'Agency',
price: 59,
description: 'For agencies running many active builds.',
live: '20 Live Websites',
staging: '20 Staging Websites',
cta: 'Buy Now',
},
{
pillLabel: '40 Websites',
name: 'Business',
price: 99,
description: 'For teams managing sites at scale.',
live: '40 Live Websites',
staging: '40 Staging Websites',
cta: 'Buy Now',
},
{
pillLabel: 'Unlimited Websites',
name: 'Unlimited',
price: null,
description: 'For agencies that need it all, no limits.',
live: 'Unlimited live websites',
staging: 'Unlimited staging websites',
cta: 'Contact sales',
},
];
const sliderMarks = [
{ value: 0, label: '1' },
{ value: 1, label: '5' },
{ value: 2, label: '10' },
{ value: 3, label: '20' },
{ value: 4, label: '40' },
{ value: 5, label: 'More' },
];
export default function PricingSliderPlanCard() {
const [tierIndex, setTierIndex] = useState(0);
const tier = tiers[tierIndex];
return (
<section aria-labelledby="pricing-slider-plan-card-title" className="w-full">
<div className="mx-auto max-w-sm">
<h4 id="pricing-slider-plan-card-title" className="text-lg font-semibold">
Choose your plan by number of websites
</h4>
<div className="mt-4 px-4">
<Slider
value={tierIndex}
onChange={(value) => setTierIndex(value)}
min={0}
max={5}
step={1}
marks={sliderMarks}
thumbAriaLabel="Number of websites"
/>
</div>
<Card
className="mt-12"
bodyClass="text-center p-8"
>
<Tag>{tier.name}</Tag>
<div className="mt-4 flex items-end justify-center gap-1.5">
<span className="text-4xl font-bold text-foreground">
{tier.price === null ? 'Custom' : `$${tier.price}`}
</span>
{tier.price !== null && (
<div className="mt-1 text-start">
/month
</div>
)}
</div>
<p className="mt-2 text-muted-foreground">{tier.description}</p>
<ul className={
`mt-4 space-y-2 text-start max-w-50 mx-auto
${tier.price === null ? 'max-w-56' : 'max-w-50'}
`
}>
<li className="flex items-start gap-2">
<PiCheckCircleFill aria-hidden="true" className="mt-0.5 shrink-0 text-lg text-success" />
<span>{tier.live}</span>
</li>
<li className="flex items-start gap-2">
<PiCheckCircleFill aria-hidden="true" className="mt-0.5 shrink-0 text-lg text-success" />
<span>{tier.staging}</span>
</li>
<li className="flex items-start gap-2">
<PiCheckCircleFill aria-hidden="true" className="mt-0.5 shrink-0 text-lg text-success" />
<span className="inline-flex items-center gap-1.5">
Every core feature
<Tooltip
title="No feature gating between plans - every tier ships the full toolkit."
tabIndex={0}
aria-label="About core features"
>
<PiInfo aria-hidden="true" className="text-base text-muted-foreground" />
</Tooltip>
</span>
</li>
<li className="flex items-start gap-2">
<PiCheckCircleFill aria-hidden="true" className="mt-0.5 shrink-0 text-lg text-success" />
<span>Ongoing updates</span>
</li>
<li className="flex items-start gap-2">
<PiCheckCircleFill aria-hidden="true" className="mt-0.5 shrink-0 text-lg text-success" />
<span>Cancel or switch anytime</span>
</li>
</ul>
<Button type="button" block variant="solid" className="mt-6">
{tier.cta}
</Button>
</Card>
</div>
</section>
);
}
Pricing 05
Preview
npx nateui@latest add PricingUpgradeProCardDark
import { useState } from 'react';
import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import Radio from '@/components/ui/Radio';
import Tag from '@/components/ui/Tag';
import IconFrame from '@/components/composites/IconFrame';
import classNames from '@/utils/classNames';
import {
PiArchive,
PiFolderPlus,
PiHeadset,
PiMonitor,
PiUsersThree,
} from 'react-icons/pi';
const assetBase = 'https://statics.nateui.com/img';
type Feature = {
icon: typeof PiUsersThree;
label: string;
};
const features: Feature[] = [
{ icon: PiUsersThree, label: 'Unlimited team members' },
{ icon: PiArchive, label: 'Unlimited storage space' },
{ icon: PiFolderPlus, label: 'Create unlimited projects' },
{ icon: PiMonitor, label: 'Advanced permission controls' },
{ icon: PiHeadset, label: 'Priority support' },
];
type BillingCycle = 'yearly' | 'monthly';
type BillingOption = {
label: string;
price: string;
detail: string;
badge?: string;
};
const billingOptions: Record<BillingCycle, BillingOption> = {
yearly: {
label: 'Yearly',
price: '$15/month',
detail: 'Renews once a year',
badge: 'Save 25%',
},
monthly: {
label: 'Monthly',
price: '$20/month',
detail: 'Renews every month',
},
};
export default function PricingUpgradeProCard() {
const [billingCycle, setBillingCycle] = useState<BillingCycle>('yearly');
return (
<section aria-labelledby="pricing-upgrade-pro-title" className="flex w-full justify-center">
<Card
className="w-full max-w-sm text-center"
bodyClass="p-8"
>
<div className="relative mx-auto h-20 w-20">
<span
aria-hidden="true"
className="absolute inset-0 m-auto h-16 w-16 rotate-12 rounded-lg border bg-muted"
/>
<span
aria-hidden="true"
className="absolute inset-0 m-auto h-16 w-16 -rotate-12 rounded-lg border bg-muted"
/>
<IconFrame
size={56}
className="absolute inset-0 m-auto shadow-card"
>
<img
src={`${assetBase}/thumbs/plans/pro.svg`}
alt=""
className="h-10 w-10 object-contain"
/>
</IconFrame>
</div>
<h4 id="pricing-upgrade-pro-title" className="mt-4 text-xl font-semibold text-foreground">
More room to grow with Pro
</h4>
<p className="mt-2 text-muted-foreground">
Upgrade for higher limits, deeper controls, and support that keeps up with your team.
</p>
<div className="mt-8 text-left">
<span className="font-semibold text-foreground">What's included:</span>
<ul className="mt-3 space-y-3">
{features.map(({ icon: Icon, label }) => (
<li key={label} className="flex items-center gap-3">
<IconFrame variant="thick" size={36}>
<Icon aria-hidden="true" className="text-lg text-foreground" />
</IconFrame>
<span className="font-medium text-foreground">{label}</span>
</li>
))}
</ul>
</div>
<Radio.Group
vertical
value={billingCycle}
onChange={(value) => setBillingCycle(value as BillingCycle)}
className="mt-8 w-full"
>
{(Object.entries(billingOptions) as [BillingCycle, BillingOption][]).map(
([key, option]) => (
<Radio
key={key}
value={key}
className={classNames(
'w-full rounded-xl border p-4',
billingCycle === key && 'border-primary ring',
)}
>
<span className="flex flex-1 items-center justify-between gap-2">
<span className="text-start">
<span className="flex items-baseline gap-1.5">
<span className="font-semibold text-foreground">
{option.label}
</span>
<span>({option.price})</span>
</span>
<span className="block text-xs text-muted-foreground">{option.detail}</span>
</span>
{option.badge && (
<Tag className="border-0 bg-primary-soft text-primary">{option.badge}</Tag>
)}
</span>
</Radio>
),
)}
</Radio.Group>
<Button type="button" block variant="solid" className="mt-8">
Upgrade now
</Button>
<Button
type="button"
variant="link"
className="mt-4 underline underline-offset-2"
>
Keep using free plan
</Button>
</Card>
</section>
);
}
Pricing 06
Preview
npx nateui@latest add PricingPlanUpgradeCardsDark
import { useState } from 'react';
import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import Segment from '@/components/ui/Segment';
import Tag from '@/components/ui/Tag';
import IconFrame from '@/components/composites/IconFrame';
import classNames from '@/utils/classNames';
import {
PiCheck,
PiStarFill
} from 'react-icons/pi';
const assetBase = 'https://statics.nateui.com/img';
type BillingCycle = 'monthly' | 'yearly';
type Plan = {
name: string;
description: string;
iconSrc: string;
monthlyPrice: number;
features: string[];
current?: boolean;
popular?: boolean;
};
const plans: Plan[] = [
{
name: 'Essentials',
description: 'Everything you need to get started.',
iconSrc: `${assetBase}/thumbs/plans/basic.svg`,
monthlyPrice: 79,
current: true,
features: [
'Access to core features',
'Real-time usage insights',
'Up to 10 team members',
'20 GB data storage',
'Standard email support',
],
},
{
name: 'Pro',
description: 'Advanced tools for growing teams.',
iconSrc: `${assetBase}/thumbs/plans/standard.svg`,
monthlyPrice: 149,
popular: true,
features: [
'Everything in Essentials',
'200+ integrations',
'Advanced analytics & reporting',
'Up to 20 team members',
'40 GB data storage',
'Priority email support',
],
},
];
export default function PricingPlanUpgradeCards() {
const [cycle, setCycle] = useState<BillingCycle>('monthly');
const yearly = cycle === 'yearly';
return (
<section aria-labelledby="pricing-plan-upgrade-title" className="w-full">
<div className="mx-auto max-w-3xl">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-start">
<div>
<h3 id="pricing-plan-upgrade-title" className="text-xl font-semibold">
Choose a plan that grows with you
</h3>
<p className="mt-1 text-muted-foreground">
Unlock more insights, more data, and more possibilities.
</p>
</div>
</div>
<Segment
value={cycle}
onChange={(value) => setCycle(value as BillingCycle)}
aria-label="Billing cycle"
>
<Segment.Item value="monthly">Monthly</Segment.Item>
<Segment.Item value="yearly">Yearly</Segment.Item>
</Segment>
</div>
<div className="mt-8 grid grid-cols-1 gap-4 md:grid-cols-2">
{plans.map((plan) => {
const price = yearly ? Math.round((plan.monthlyPrice * 12)) : plan.monthlyPrice;
return (
<Card
key={plan.name}
className={plan.popular ? 'border-primary ring' : undefined}
bodyClass="flex h-full flex-col"
>
<div className="flex items-start justify-between gap-2">
<IconFrame size={40}>
<img src={plan.iconSrc} alt="" className="h-6 w-6 object-contain" />
</IconFrame>
{plan.popular && (
<Tag
className="border-0 bg-primary-soft text-primary gap-1"
>
<PiStarFill aria-hidden="true" className="text-primary" />
<span>Most popular</span>
</Tag>
)}
</div>
<h4 className="mt-4 text-xl font-semibold text-foreground">{plan.name}</h4>
<p className="mt-1 text-muted-foreground">{plan.description}</p>
<div className="mt-4 flex items-baseline gap-1.5">
<span className="text-3xl font-bold text-foreground">${price}</span>
<span>/
{yearly ? 'year' : 'month'}
</span>
</div>
<ul className="mt-8 flex-1 space-y-3">
{plan.features.map((feature) => (
<li key={feature} className="flex items-center gap-3">
<span className="flex h-5 w-5 items-center justify-center rounded-md bg-muted">
<PiCheck aria-hidden="true" className="shrink-0 text-foreground" />
</span>
<span className="text-foreground">{feature}</span>
</li>
))}
</ul>
<Button
type="button"
block
variant={plan.current ? 'default' : 'solid'}
disabled={plan.current}
className={classNames(
'mt-8',
plan.popular && 'bg-gradient-to-br from-primary to-primary/70',
)}
>
{plan.current ? 'Your current plan' : `Upgrade to ${plan.name}`}
</Button>
</Card>
);
})}
</div>
</div>
</section>
);
}
Pricing 07
Preview
npx nateui@latest add PricingCreditPackSelectorDark
import { useState } from 'react';
import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import Tag from '@/components/ui/Tag';
import IconFrame from '@/components/composites/IconFrame';
import classNames from '@/utils/classNames';
import { PiCheck, PiCheckCircle, PiInfo, PiSparkleFill } from 'react-icons/pi';
type CreditPack = {
name: string;
credits: number;
usageLabel: string;
price: number;
tagline: string;
badge?: string;
};
const packs: CreditPack[] = [
{
name: 'Lite Pack',
credits: 150,
usageLabel: 'Occasional use',
price: 79,
tagline: 'Enough headroom for a side project.',
},
{
name: 'Plus Pack',
credits: 400,
usageLabel: 'Everyday use',
price: 179,
tagline: 'Room to scale without watching the meter.',
badge: 'Most Popular',
},
{
name: 'Max Pack',
credits: 800,
usageLabel: 'Nonstop use',
price: 319,
tagline: 'Built for teams running at full throttle.',
badge: 'Best Rate',
},
];
const highlights = [
{
title: 'No request caps',
description: 'Send as much as your workflow needs, with no daily ceiling.',
},
{
title: 'Real-time processing',
description: 'Requests complete in seconds - nothing sits in a queue.',
},
{
title: 'Direct team access',
description: 'A shared channel straight to the people who build it.',
},
];
export default function PricingCreditPackSelector() {
const [selectedIndex, setSelectedIndex] = useState(0);
const selectedPack = packs[selectedIndex];
return (
<section aria-labelledby="pricing-credit-pack-title" className="w-full">
<div className="mx-auto grid max-w-4xl grid-cols-1 gap-8 md:grid-cols-2">
<div>
<Tag>Credit Packs</Tag>
<h4 id="pricing-credit-pack-title" className="mt-4 text-xl font-semibold">
Credits that work your way
</h4>
<p className="mt-2 text-muted-foreground">
Buy credits once and use them whenever you need them. No subscriptions, no expiry
games.
</p>
<div className="mt-8 space-y-2">
{packs.map((pack, index) => {
const isSelected = index === selectedIndex;
return (
<button
key={pack.name}
type="button"
onClick={() => setSelectedIndex(index)}
aria-pressed={isSelected}
className={classNames(
'flex w-full items-center justify-between gap-4 rounded-card border bg-card p-4 text-left transition-colors',
isSelected ? 'border-primary ring' : 'hover:bg-muted',
)}
>
<span>
<span className="block font-semibold text-foreground">{pack.name}</span>
<span className="mt-1 block">
{pack.credits} Credits • {pack.usageLabel}
</span>
</span>
{isSelected ? (
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary">
<PiCheck aria-hidden="true" className="text-xs text-primary-foreground" />
</span>
) : (
<span className="h-5 w-5 shrink-0 rounded-full border-2 border" />
)}
</button>
);
})}
</div>
</div>
<Card bodyClass="p-8">
<div className="flex items-start justify-between gap-2">
<div className="flex items-center gap-3">
<IconFrame size={40}>
<PiSparkleFill aria-hidden="true" className="text-lg text-foreground" />
</IconFrame>
<h4 className="text-xl font-semibold text-foreground">
{selectedPack.credits} Credits
</h4>
</div>
</div>
<div className="mt-4 flex items-baseline gap-1.5">
<span className="text-3xl font-bold text-foreground">
${selectedPack.price}
</span>
<span>/ one time</span>
</div>
<Button type="button" block variant="solid" className="mt-4">
Get {selectedPack.credits} Credits
</Button>
<p className="mt-3 text-center text-sm text-muted-foreground">{selectedPack.tagline}</p>
<div className="mt-8 border-t pt-8">
<span className="text-sm font-semibold text-foreground">Highlights:</span>
<ul className="mt-3 space-y-3">
{highlights.map((item) => (
<li key={item.title} className="flex items-start gap-3">
<PiCheckCircle aria-hidden="true" className="mt-0.5 shrink-0 text-lg text-foreground" />
<span>
<span className="block text-sm font-medium text-foreground">{item.title}</span>
<span className="text-sm text-muted-foreground">{item.description}</span>
</span>
</li>
))}
</ul>
</div>
<div className="mt-8 flex items-start gap-2 border-t pt-4 text-xs text-muted-foreground">
<PiInfo aria-hidden="true" className="mt-0.5 shrink-0" />
<span>
Packs add usage headroom only — plan-level features stay tied to your
subscription tier.
</span>
</div>
</Card>
</div>
</section>
);
}