Pricing Section
A responsive pricing tier grid with highlighted plan, feature lists, and per-tier CTAs. The canonical complex marketing block for SaaS and product sites.
Preview
Simple, Transparent Pricing
Start free. Scale as you grow. No hidden fees.
Starter
Perfect for side projects and personal use.
- Up to 3 projects
- Basic analytics
- Community support
Pro
For growing teams that need more power.
- Unlimited projects
- Advanced analytics
- Priority support
- Custom domains
Enterprise
Tailored solutions for large organizations.
- Everything in Pro
- Dedicated support
- SLA guarantee
- Custom contracts
Installation
npx visor add --block pricing-sectionThis copies files into your project:
blocks/pricing-section/pricing-section.tsx— the block componentblocks/pricing-section/pricing-section.module.css— the styles
Usage
import { PricingSection } from '@/blocks/pricing-section/pricing-section';
import type { PricingTier } from '@/blocks/pricing-section/pricing-section';
const tiers: PricingTier[] = [
{
name: "Starter",
price: "Free",
features: ["Up to 3 projects", "Basic analytics"],
buttonText: "Get Started",
buttonHref: "/signup",
},
{
name: "Pro",
price: "$29",
period: "/mo",
features: ["Unlimited projects", "Advanced analytics", "Priority support"],
buttonText: "Start Free Trial",
buttonHref: "/signup/pro",
highlighted: true,
badge: "Most Popular",
},
];
export default function PricingPage() {
return (
<PricingSection
heading="Simple, Transparent Pricing"
description="No hidden fees. Cancel anytime."
tiers={tiers}
/>
);
}With click callbacks
<PricingSection
heading="Choose Your Plan"
tiers={[
{
name: "Pro",
price: "$29",
period: "/mo",
features: ["All features", "Priority support"],
buttonText: "Upgrade Now",
onButtonClick: () => openUpgradeModal("pro"),
highlighted: true,
},
]}
/>With formatted prices
Use the use-currency hook to format prices before passing them as strings:
import { useCurrency } from '@/hooks/use-currency/use-currency';
function PricingWithCurrency() {
const { format } = useCurrency();
const tiers: PricingTier[] = [
{
name: "Pro",
price: format(2900), // "$29.00"
period: "/mo",
features: ["All features"],
buttonText: "Get Pro",
highlighted: true,
},
];
return <PricingSection tiers={tiers} />;
}Responsive Behavior
The grid adapts to the number of tiers:
- Mobile (below 640px): Single column — tiers stack vertically
- Tablet (640–1023px): Two columns — tiers display side by side
- Desktop (1024px+):
auto-fitwithminmax(240px, 1fr)— adapts naturally to tier count without hardcoding a column count
Highlighted Tier
Set highlighted: true on any tier to apply the accent treatment:
- Border color switches to
var(--border-focus)(theme-aware) - Background switches to
var(--surface-accent-subtle)(theme-aware) - Button renders with
variant="default"instead ofvariant="outline" - An optional
badgestring renders above the tier name (e.g., "Most Popular")
Composition
This block composes these Visor components:
- Card (+ CardHeader, CardContent, CardFooter) — Tier container
- Badge — Optional "Most Popular" label
- Button — Per-tier CTA; renders as
<a>whenbuttonHrefis provided - Heading — Section heading
- Text — Section description and tier descriptions
- Check (Phosphor icon) — Feature list check marks
Props
PricingSectionProps
| Prop | Type | Required | Description |
|---|---|---|---|
tiers | PricingTier[] | Yes | Array of pricing tier objects to render |
heading | string | No | Optional section heading above the grid |
description | string | No | Optional supporting text below the heading |
className | string | No | Additional CSS class applied to the root <section> |
PricingTier
| Prop | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Tier name (e.g. "Starter", "Pro", "Enterprise") |
price | string | Yes | Formatted price string (e.g. "$29", "Free", "Custom") |
period | string | No | Billing period shown next to price (e.g. "/mo", "/year") |
description | string | No | Short description below the price |
features | string[] | Yes | Feature list items, each rendered with a Check icon |
buttonText | string | Yes | CTA button label |
buttonHref | string | No | If provided, renders the button as an anchor tag |
onButtonClick | () => void | No | Click handler (used when no buttonHref) |
highlighted | boolean | No | Applies accent border, subtle background, and default button variant |
badge | string | No | Optional badge label shown above the tier name |
color | string | No | Per-tier accent color (CSS color value). Colors the tier name, checkmarks, and hover/highlight glow. Falls back to --accent |
About Blocks
Blocks are complete UI patterns made up of multiple Visor components. Unlike individual components, blocks represent larger, composed sections of UI. Blocks are copy-and-own — install them into your project and customize freely.