Blocks · Marketing
Pricing
Plans side by side; the featured plan inverts, an enterprise plan can run as a wide row under them.
19% of the 2,062 sites in The UI Index have a pricing (382 sites).
Simple pricing
Start free. Upgrade when your team needs more.
Hobby
€0/month
For side projects.
- 1 project
- Community support
- Basic analytics
Pro
Popular€19/month
For a growing product.
- Unlimited projects
- Email support
- Advanced analytics
- Custom domain
Team
€49/month
For teams of up to 20.
- Everything in Pro
- Roles and permissions
- Shared workspaces
- Priority support
Enterprise
For organisations with their own security review and billing.
- SAML SSO and SCIM
- Audit log
- Uptime SLA
- Invoice billing
Simple pricing
Start free. Upgrade when your team needs more.
Hobby
€0/month
For side projects.
- 1 project
- Community support
- Basic analytics
Pro
Popular€19/month
For a growing product.
- Unlimited projects
- Email support
- Advanced analytics
- Custom domain
Team
€49/month
For teams of up to 20.
- Everything in Pro
- Roles and permissions
- Shared workspaces
- Priority support
Enterprise
For organisations with their own security review and billing.
- SAML SSO and SCIM
- Audit log
- Uptime SLA
- Invoice billing
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ pricing.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ astro/ pricing.json
Or copy the files below into your project. They need clsx, tailwind-merge and the components they import, and the Caseroom theme. The HTML needs only the compiled CSS.
export const pricing = {
// @container: the plans go side by side when the block is wide enough, wherever it sits.
root: '@container bg-background px-6 py-20 sm:py-24',
head: 'mx-auto max-w-2xl text-center',
title: 'text-3xl font-semibold tracking-[-0.03em] text-balance sm:text-4xl',
description: 'mt-4 text-base text-pretty text-muted-foreground',
grid: 'mx-auto mt-12 grid max-w-5xl gap-4 @xl:grid-cols-3',
tier: 'flex flex-col rounded-xl border bg-card p-5 text-card-foreground @4xl:p-6',
// The featured plan is an inverted ground (class inverted, from the theme): its button, ring and states keep
// their contrast because every token flips with it.
featured: 'inverted border-transparent shadow-xl',
name: 'text-sm font-medium',
price: 'mt-4 flex items-baseline gap-1',
amount: 'text-4xl font-semibold tracking-[-0.03em] tabular-nums',
period: 'text-sm opacity-70',
tierDescription: 'mt-3 text-sm opacity-75',
features: 'mt-6 grid gap-2.5 text-sm',
feature: 'flex gap-2.5',
check: 'mt-0.5 size-4 shrink-0',
cta: 'mt-8 w-full',
// The enterprise row: one wide card under the plans for the deal without a list price. A row, not a fourth
// column, so the plans keep their width; it stacks on narrow blocks and puts the action at the end when wide.
enterprise:
'mx-auto mt-4 flex max-w-5xl flex-col gap-5 rounded-xl border bg-card p-5 text-card-foreground @2xl:flex-row @2xl:items-center @2xl:justify-between @2xl:gap-8 @4xl:p-6',
enterpriseFeatures: 'mt-4 flex flex-wrap gap-x-5 gap-y-2 text-sm',
enterpriseCta: 'w-full shrink-0 @2xl:w-auto',
};
export type PricingTier = {
name: string;
price: string;
period?: string;
description?: string;
features: string[];
cta: { label: string; href: string };
/** One tier at most: shown inverted (black on light pages, white on dark ones). */
featured?: boolean;
badge?: string;
};
/** The plan without a list price ("Enterprise", "Custom"), shown as a wide row under the plans. */
export type PricingEnterprise = {
name: string;
description?: string;
/** A few short lines; they run in one row when there is room. */
features?: string[];
cta: { label: string; href: string };
};
export type PricingContent = { title: string; description?: string; tiers: PricingTier[]; enterprise?: PricingEnterprise };import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { pricing, type PricingContent } from './pricing.classes';
export type PricingProps = PricingContent & { className?: string };
/** Two to four plans side by side; the featured plan inverts. Prices are text, so any currency or "Custom" works;
* the enterprise plan can sit under them as one wide row. */
export function Pricing({ title, description, tiers, enterprise, className }: PricingProps) {
return (
<section className={cn(pricing.root, className)}>
<div className={pricing.head}>
<h2 className={pricing.title}>{title}</h2>
{description && <p className={pricing.description}>{description}</p>}
</div>
<ul className={pricing.grid}>
{tiers.map((tier) => (
<li key={tier.name} className={cn(pricing.tier, tier.featured && pricing.featured)}>
<div className="flex items-center justify-between gap-2">
<h3 className={pricing.name}>{tier.name}</h3>
{tier.badge && <Badge variant={tier.featured ? 'secondary' : 'outline'}>{tier.badge}</Badge>}
</div>
<p className={pricing.price}>
<span className={pricing.amount}>{tier.price}</span>
{tier.period && <span className={pricing.period}>{tier.period}</span>}
</p>
{tier.description && <p className={pricing.tierDescription}>{tier.description}</p>}
<ul className={pricing.features}>
{tier.features.map((feature) => (
<li key={feature} className={pricing.feature}>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={pricing.check}>
<path d="M3.5 8.5l3 3 6-7" />
</svg>
{feature}
</li>
))}
</ul>
<div className="mt-auto">
<Button href={tier.cta.href} variant={tier.featured ? 'default' : 'outline'} className={pricing.cta}>
{tier.cta.label}
</Button>
</div>
</li>
))}
</ul>
{enterprise && (
<div className={pricing.enterprise}>
<div>
<h3 className="text-base font-semibold">{enterprise.name}</h3>
{enterprise.description && <p className="mt-1 text-sm text-pretty text-muted-foreground">{enterprise.description}</p>}
{enterprise.features && (
<ul className={pricing.enterpriseFeatures}>
{enterprise.features.map((feature) => (
<li key={feature} className={pricing.feature}>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={pricing.check}>
<path d="M3.5 8.5l3 3 6-7" />
</svg>
{feature}
</li>
))}
</ul>
)}
</div>
<Button href={enterprise.cta.href} variant="outline" className={pricing.enterpriseCta}>
{enterprise.cta.label}
</Button>
</div>
)}
</section>
);
}---
import Badge from '@/components/ui/badge.astro';
import Button from '@/components/ui/button.astro';
import { cn } from '@/lib/utils';
import { pricing, type PricingContent } from './pricing.classes';
type Props = PricingContent & { class?: string };
const { title, description, tiers, enterprise, class: className } = Astro.props;
---
<section class={cn(pricing.root, className)}>
<div class={pricing.head}>
<h2 class={pricing.title}>{title}</h2>
{description && <p class={pricing.description}>{description}</p>}
</div>
<ul class={pricing.grid}>
{
tiers.map((tier) => (
<li class={cn(pricing.tier, tier.featured && pricing.featured)}>
<div class="flex items-center justify-between gap-2">
<h3 class={pricing.name}>{tier.name}</h3>
{tier.badge && <Badge variant={tier.featured ? 'secondary' : 'outline'}>{tier.badge}</Badge>}
</div>
<p class={pricing.price}>
<span class={pricing.amount}>{tier.price}</span>
{tier.period && <span class={pricing.period}>{tier.period}</span>}
</p>
{tier.description && <p class={pricing.tierDescription}>{tier.description}</p>}
<ul class={pricing.features}>
{tier.features.map((feature) => (
<li class={pricing.feature}>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class={pricing.check}>
<path d="M3.5 8.5l3 3 6-7" />
</svg>
{feature}
</li>
))}
</ul>
<div class="mt-auto">
<Button href={tier.cta.href} variant={tier.featured ? 'default' : 'outline'} class={pricing.cta}>
{tier.cta.label}
</Button>
</div>
</li>
))
}
</ul>
{
enterprise && (
<div class={pricing.enterprise}>
<div>
<h3 class="text-base font-semibold">{enterprise.name}</h3>
{enterprise.description && <p class="mt-1 text-sm text-pretty text-muted-foreground">{enterprise.description}</p>}
{enterprise.features && (
<ul class={pricing.enterpriseFeatures}>
{enterprise.features.map((feature) => (
<li class={pricing.feature}>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class={pricing.check}>
<path d="M3.5 8.5l3 3 6-7" />
</svg>
{feature}
</li>
))}
</ul>
)}
</div>
<Button href={enterprise.cta.href} variant="outline" class={pricing.enterpriseCta}>
{enterprise.cta.label}
</Button>
</div>
)
}
</section>export const pricing = {
// @container: the plans go side by side when the block is wide enough, wherever it sits.
root: '@container bg-background px-6 py-20 sm:py-24',
head: 'mx-auto max-w-2xl text-center',
title: 'text-3xl font-semibold tracking-[-0.03em] text-balance sm:text-4xl',
description: 'mt-4 text-base text-pretty text-muted-foreground',
grid: 'mx-auto mt-12 grid max-w-5xl gap-4 @xl:grid-cols-3',
tier: 'flex flex-col rounded-xl border bg-card p-5 text-card-foreground @4xl:p-6',
// The featured plan is an inverted ground (class inverted, from the theme): its button, ring and states keep
// their contrast because every token flips with it.
featured: 'inverted border-transparent shadow-xl',
name: 'text-sm font-medium',
price: 'mt-4 flex items-baseline gap-1',
amount: 'text-4xl font-semibold tracking-[-0.03em] tabular-nums',
period: 'text-sm opacity-70',
tierDescription: 'mt-3 text-sm opacity-75',
features: 'mt-6 grid gap-2.5 text-sm',
feature: 'flex gap-2.5',
check: 'mt-0.5 size-4 shrink-0',
cta: 'mt-8 w-full',
// The enterprise row: one wide card under the plans for the deal without a list price. A row, not a fourth
// column, so the plans keep their width; it stacks on narrow blocks and puts the action at the end when wide.
enterprise:
'mx-auto mt-4 flex max-w-5xl flex-col gap-5 rounded-xl border bg-card p-5 text-card-foreground @2xl:flex-row @2xl:items-center @2xl:justify-between @2xl:gap-8 @4xl:p-6',
enterpriseFeatures: 'mt-4 flex flex-wrap gap-x-5 gap-y-2 text-sm',
enterpriseCta: 'w-full shrink-0 @2xl:w-auto',
};
export type PricingTier = {
name: string;
price: string;
period?: string;
description?: string;
features: string[];
cta: { label: string; href: string };
/** One tier at most: shown inverted (black on light pages, white on dark ones). */
featured?: boolean;
badge?: string;
};
/** The plan without a list price ("Enterprise", "Custom"), shown as a wide row under the plans. */
export type PricingEnterprise = {
name: string;
description?: string;
/** A few short lines; they run in one row when there is room. */
features?: string[];
cta: { label: string; href: string };
};
export type PricingContent = { title: string; description?: string; tiers: PricingTier[]; enterprise?: PricingEnterprise };<section class="@container bg-background px-6 py-20 sm:py-24">
<div class="mx-auto max-w-2xl text-center">
<h2 class="text-3xl font-semibold tracking-[-0.03em] text-balance sm:text-4xl">Simple pricing</h2>
<p class="mt-4 text-base text-pretty text-muted-foreground">Start free. Upgrade when your team needs more.</p>
</div>
<ul class="mx-auto mt-12 grid max-w-5xl gap-4 @xl:grid-cols-3">
<li class="flex flex-col rounded-xl border bg-card p-5 text-card-foreground @4xl:p-6">
<div class="flex items-center justify-between gap-2">
<h3 class="text-sm font-medium">Hobby</h3>
</div>
<p class="mt-4 flex items-baseline gap-1">
<span class="text-4xl font-semibold tracking-[-0.03em] tabular-nums">€0</span>
<span class="text-sm opacity-70">/month</span>
</p>
<p class="mt-3 text-sm opacity-75">For side projects.</p>
<ul class="mt-6 grid gap-2.5 text-sm">
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
1 project
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Community support
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Basic analytics
</li>
</ul>
<div class="mt-auto">
<a href="#" class="inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,transform] select-none active:scale-[0.97] aria-busy:cursor-progress disabled:pointer-events-none disabled:opacity-50 aria-busy:disabled:opacity-80 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background hover:bg-accent hover:text-accent-foreground active:bg-pressed h-9 px-4 mt-8 w-full">
Start free
</a>
</div>
</li>
<li class="flex flex-col rounded-xl border bg-card p-5 text-card-foreground @4xl:p-6 inverted border-transparent shadow-xl">
<div class="flex items-center justify-between gap-2">
<h3 class="text-sm font-medium">Pro</h3>
<span class="inline-flex w-fit shrink-0 items-center gap-1 rounded-sm border px-1.5 py-0.5 text-xs font-medium whitespace-nowrap [&_svg]:size-3 border-transparent bg-secondary text-secondary-foreground">
Popular
</span>
</div>
<p class="mt-4 flex items-baseline gap-1">
<span class="text-4xl font-semibold tracking-[-0.03em] tabular-nums">€19</span>
<span class="text-sm opacity-70">/month</span>
</p>
<p class="mt-3 text-sm opacity-75">For a growing product.</p>
<ul class="mt-6 grid gap-2.5 text-sm">
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Unlimited projects
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Email support
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Advanced analytics
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Custom domain
</li>
</ul>
<div class="mt-auto">
<a href="#" class="inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,transform] select-none active:scale-[0.97] aria-busy:cursor-progress disabled:pointer-events-none disabled:opacity-50 aria-busy:disabled:opacity-80 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] h-9 px-4 mt-8 w-full">
Start a trial
</a>
</div>
</li>
<li class="flex flex-col rounded-xl border bg-card p-5 text-card-foreground @4xl:p-6">
<div class="flex items-center justify-between gap-2">
<h3 class="text-sm font-medium">Team</h3>
</div>
<p class="mt-4 flex items-baseline gap-1">
<span class="text-4xl font-semibold tracking-[-0.03em] tabular-nums">€49</span>
<span class="text-sm opacity-70">/month</span>
</p>
<p class="mt-3 text-sm opacity-75">For teams of up to 20.</p>
<ul class="mt-6 grid gap-2.5 text-sm">
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Everything in Pro
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Roles and permissions
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Shared workspaces
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Priority support
</li>
</ul>
<div class="mt-auto">
<a href="#" class="inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,transform] select-none active:scale-[0.97] aria-busy:cursor-progress disabled:pointer-events-none disabled:opacity-50 aria-busy:disabled:opacity-80 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background hover:bg-accent hover:text-accent-foreground active:bg-pressed h-9 px-4 mt-8 w-full">
Choose Team
</a>
</div>
</li>
</ul>
<div class="mx-auto mt-4 flex max-w-5xl flex-col gap-5 rounded-xl border bg-card p-5 text-card-foreground @2xl:flex-row @2xl:items-center @2xl:justify-between @2xl:gap-8 @4xl:p-6">
<div>
<h3 class="text-base font-semibold">Enterprise</h3>
<p class="mt-1 text-sm text-pretty text-muted-foreground">For organisations with their own security review and billing.</p>
<ul class="mt-4 flex flex-wrap gap-x-5 gap-y-2 text-sm">
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
SAML SSO and SCIM
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Audit log
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Uptime SLA
</li>
<li class="flex gap-2.5">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="mt-0.5 size-4 shrink-0"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Invoice billing
</li>
</ul>
</div>
<a href="#" class="inline-flex cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,transform] select-none active:scale-[0.97] aria-busy:cursor-progress disabled:pointer-events-none disabled:opacity-50 aria-busy:disabled:opacity-80 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background hover:bg-accent hover:text-accent-foreground active:bg-pressed h-9 px-4 w-full shrink-0 @2xl:w-auto">
Talk to sales
</a>
</div>
</section>