# Pricing Page

> A React SaaS pricing page: plans with monthly or yearly and USD or EUR switches, a comparison table, quotes, an FAQ and a closing call.

- Type: React block (Blocks)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.2.0
- Install: `npx wingo-ui@latest add pricing-page`
- Docs: https://wingo-ui.com/components/pricing-page

## Interaction

Switch the billing period between Monthly and Yearly (the yearly option carries a "2 months free" badge) and every price rolls to its new value; switch the currency between dollars and euros and the prices convert at eurRate, rounded to whole euros. Each plan has one action: it spins while onSelectPlan runs, and without a handler a toast confirms the choice ("Pro plan, yearly"). Hover, focus or tap the (i) next to a feature or comparison row to read its hint. Below the plans the comparison is a table with a sticky row of plans on desktop and a plan picker with one list on phones. The FAQ is an accordion: Enter or Space opens a question, arrow keys move between them. On phones the header keeps the logo, Try for free and a menu button that opens a bottom sheet with the links (drag the handle, tap outside or press Escape to close); the plans become a swipeable carousel that starts on the featured plan (or a plain stack), the quotes a snap rail, the FAQ one column and the closing buttons stack. Tab starts with a skip link to the content. Sections rise in once on mount and the closing band as it scrolls into view; reduced motion keeps everything still.

## Installation

```bash
npx wingo-ui@latest add pricing-page
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/pricing-page` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/blocks/pricing-page.tsx`
- `components/blocks/pricing-page-data.ts`

npm dependencies: `npm i motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Marketing Section](https://wingo-ui.com/components/marketing-section) ([Markdown](https://wingo-ui.com/components/marketing-section.md))
- [Accordion](https://wingo-ui.com/components/accordion) ([Markdown](https://wingo-ui.com/components/accordion.md))
- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Card](https://wingo-ui.com/components/card) ([Markdown](https://wingo-ui.com/components/card.md))
- [Drawer](https://wingo-ui.com/components/drawer) ([Markdown](https://wingo-ui.com/components/drawer.md))
- [Pricing table](https://wingo-ui.com/components/pricing-table) ([Markdown](https://wingo-ui.com/components/pricing-table.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Separator](https://wingo-ui.com/components/separator) ([Markdown](https://wingo-ui.com/components/separator.md))
- [Toast](https://wingo-ui.com/components/toast) ([Markdown](https://wingo-ui.com/components/toast.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
// app/(marketing)/pricing/page.tsx
"use client"

import Link from "next/link"
import { useRouter } from "next/navigation"
import { PricingPage } from "@/components/blocks/pricing-page"

export default function PricingRoute() {
  const router = useRouter()
  return (
    <PricingPage
      linkComponent={Link}
      defaultPeriod="yearly"
      onSelectPlan={(planId, period, currency) =>
        router.push(`/sign-up?plan=${planId}&period=${period}&currency=${currency}`)
      }
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `plans?` | `PricingPlan[]` |  | The plans, priced in USD per user per month; defaults to PRICING_PAGE_SAMPLE.plans (Free, Start, Pro featured, Business). |
| `comparison?` | `PricingComparisonGroup[]` |  | The comparison table below the plans; defaults to PRICING_PAGE_SAMPLE.comparison (5 groups, 24 rows). |
| `faqs?` | `PricingFaq[]` |  | { question, answer } pairs for the FAQ accordion; links inside answers are underlined. Defaults to PRICING_PAGE_SAMPLE.faqs (8 questions). |
| `testimonials?` | `PricingTestimonial[]` |  | { quote, name, role, company, avatar? } cards; defaults to PRICING_PAGE_SAMPLE.testimonials (3 quotes). |
| `logos?` | `string[]` |  | Customer wordmarks under the plans; defaults to PRICING_PAGE_SAMPLE.logos. |
| `labels?` | `Partial<PricingPageLabels>` |  | Every visible string (English by default, DEFAULT_PRICING_PAGE_LABELS). labels.pricingTable passes strings to the plans table. |
| `hrefs?` | `Partial<PricingPageHrefs>` |  | Where the header, the closing band and the FAQ link go: home, current (the nav link marked current), signIn, signUp, demo, contact. |
| `period?` | `"monthly" \| "yearly"` |  | Controlled billing period; pair it with onPeriodChange. |
| `defaultPeriod?` | `"monthly" \| "yearly"` | `"yearly"` | The billing period the page opens on while uncontrolled. |
| `onPeriodChange?` | `(period: BillingPeriod) => void` |  | Called when the period switch changes. |
| `currency?` | `string` | `"USD"` | The currency the page opens on; the switch changes it afterwards. |
| `currencies?` | `string[]` | `"[\"USD\", \"EUR\"]"` | The options of the currency switch; a single entry hides it. |
| `eurRate?` | `number` | `1.08` | Units of the base currency per euro for the conversion; euro prices round to whole euros per plan. |
| `design?` | `"classic" \| "bold"` | `"classic"` | The page's look. classic is the original page; bold has heavy display type over a grid page background with a tinted logo band, an inverted featured plan with a strong ring, 28px cards, larger prices, FAQ cards and a full-width closing band. |
| `featuredStyle?` | `"raised" \| "outline" \| "inverted"` | `"raised"` | How the featured plan stands out, passed to PricingTable. Applies to the classic design; bold always inverts it. |
| `background?` | `"plain" \| "soft" \| "glow" \| "aurora" \| "grid" \| "dots"` | `"glow"` | The main background: plain is the bare page color; the others tint the page and add a glow, an aurora, a grid or dots over the first screen. A design picks its own unless you set one (classic: glow; bold: grid). |
| `backgroundColor?` | `string` | `"oklch(0.62 0.14 275)"` | The tint and glow color of the main background. |
| `bandBackground?` | `"muted" \| "tinted" \| "gradient" \| "dots"` | `"muted"` | The secondary background of the logo band (classic: muted; bold: tinted). Cards, fields and tiles inside a band are lifted one step above it, so they never share its color. |
| `bandColor?` | `string` | `"oklch(0.62 0.14 275)"` | The tint of tinted and gradient bands; defaults to backgroundColor. |
| `mobileLayout?` | `"carousel" \| "stack"` | `"carousel"` | Plans on phones: a swipeable carousel that starts on the featured plan, or a plain stack. |
| `showComparison?` | `boolean` | `true` | The full comparison under the plans. |
| `showLogos?` | `boolean` | `true` | The customer wordmarks band. |
| `showTestimonials?` | `boolean` | `true` | The three customer quotes. |
| `showFaq?` | `boolean` | `true` | The FAQ section. |
| `showCta?` | `boolean` | `true` | The closing call to action band. |
| `header?` | `boolean` | `true` | The sticky marketing header with its phone menu; turn it off when the page sits in a site layout. |
| `footer?` | `boolean` | `true` | The marketing footer with link columns. |
| `ctaTone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The featured plan, its button, the hero glow and the closing band's main action. brand is the marketing orange. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The switches' thumbs and the FAQ's open state. |
| `radius?` | `"sm" \| "md" \| "lg"` | `"md"` | Surface radius of the plan cards, quote cards and the closing band. |
| `loading?` | `boolean` | `false` | Skeleton plan cards and quotes in the real layout; the switches wait. |
| `animateIn?` | `boolean` | `true` | Sections rise in once on mount and the closing band as it scrolls into view; off in thumbnails. |
| `locale?` | `string` | `"en-US"` | Intl locale for the prices. |
| `onSelectPlan?` | `(planId: string, period: BillingPeriod, currency: string) => (void \| Promise<unknown>)` |  | A plan's action; a returned promise keeps its button loading. Without it the page waits 900 ms and toasts "Pro plan, yearly". A plan's own cta.onClick wins. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` | `"<a>"` | The router link used by the header, nav, buttons and footer (next/link in an app). |
| `className?` | `string` |  | Classes for the root. |
| `classNames?` | `Partial<Record<PricingPageSlot, string>>` |  | Classes for header, hero, switches, plans, logos, testimonials, faq, cta and footer. |

## Parts

### PRICING_PAGE_DESIGNS (constant)

The design names in order: ["classic", "bold"].

### PricingPageDesign (type)

"classic" | "bold", the values of the design prop.

### DEFAULT_PRICING_PAGE_LABELS (constant)

The English strings for every visible label, the default of labels. Type: PricingPageLabels.

### PRICING_PAGE_LABELS_EN (constant)

The same English strings under their older name, kept for existing imports.

### PRICING_PAGE_SAMPLE (constant)

The demo data: plans, the comparison table, FAQs, testimonials, customer logos and the EUR rate (1.08).

### PRICING_PAGE_SAMPLE_EN (constant)

The same sample under its older name, kept for existing imports.

### PricingPageProps (type)

Props of PricingPage, including the div attributes of the root.

## Examples

- **Brand call to action**: ctaTone brand with the inverted featured plan: the one orange action of a marketing page.
- **Monthly, in euro**: Starts on monthly billing with euro prices, each plan rounded to whole euros.
- **Plans only**: Every section, the header and the footer off: the plans inside an existing site layout.
- **Loading**: Skeleton plan cards and quotes in the real layout while prices load.
- **British English, euro**: The same page with locale en-GB and euro prices.
- **Stacked on phones**: mobileLayout stack, outlined featured plan and larger surfaces: every plan in one column below 768px.
- **Bold design**: design bold: heavy start-aligned type over a grid, an inverted featured plan with a ring, FAQ cards and a full-width closing band.

## See also

- [Pricing table](https://wingo-ui.com/components/pricing-table) ([Markdown](https://wingo-ui.com/components/pricing-table.md))
- [Accordion](https://wingo-ui.com/components/accordion) ([Markdown](https://wingo-ui.com/components/accordion.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Drawer](https://wingo-ui.com/components/drawer) ([Markdown](https://wingo-ui.com/components/drawer.md))
- [Login Page](https://wingo-ui.com/components/login-page) ([Markdown](https://wingo-ui.com/components/login-page.md))

---

Canonical: https://wingo-ui.com/components/pricing-page
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
