# Pricing table

> A React pricing table: plans side by side with a monthly / yearly switch whose prices roll, a featured plan and a full comparison table.

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

## Interaction

Flip Monthly / Yearly (tap, drag the thumb, or arrow keys once it has focus) and every price rolls to its new value while the billed line cross-fades; a screen reader hears the featured plan's new price once. Each plan's button runs its action and shows a spinner while a returned promise is pending; the current plan shows a check instead. Hover, focus or click an (i) for its hint, or tap it on a phone (tap it again, tap outside or press Escape to close); a screen reader hears it as More info plus the feature it explains. On a phone the plans are swipeable cards that start at the featured one (drag, tap a dot, or Left / Right with focus inside), and the comparison becomes a plan switcher above one column of rows, ending in the picked plan's button. On a laptop the comparison is a real table whose header of plan names and buttons sticks while you scroll.

## Installation

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

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-table` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/pricing-table.tsx`

npm dependencies: `npm i motion lucide-react`

Wingo UI dependencies (installed automatically):

- [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))
- [Carousel](https://wingo-ui.com/components/carousel) ([Markdown](https://wingo-ui.com/components/carousel.md))
- [Price](https://wingo-ui.com/components/price) ([Markdown](https://wingo-ui.com/components/price.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Table](https://wingo-ui.com/components/table) ([Markdown](https://wingo-ui.com/components/table.md))
- [Tooltip](https://wingo-ui.com/components/tooltip) ([Markdown](https://wingo-ui.com/components/tooltip.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [useLocale](https://wingo-ui.com/components/use-locale) ([Markdown](https://wingo-ui.com/components/use-locale.md))
- [Commerce](https://wingo-ui.com/components/commerce) ([Markdown](https://wingo-ui.com/components/commerce.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.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
import { PricingTable, type PricingPlan } from "@/components/ui/pricing-table"

const plans: PricingPlan[] = [
  { id: "start", name: "Start", price: 0, features: ["5 active listings", "Basic stats"] },
  {
    id: "pro",
    name: "Pro",
    price: { monthly: 9, yearly: 90 },
    featured: true,
    badge: "Most popular",
    features: ["50 active listings", "3 promotions a month", "Priority support"],
    note: "14 days free, no card",
  },
  { id: "business", name: "Business", price: { monthly: 29, yearly: 290 }, features: ["Unlimited listings"] },
]

export function SellerPlans() {
  return (
    <PricingTable
      plans={plans}
      currency="USD"
      tone="brand"
      labels={{ save: (_, formatted) => formatted, choose: (plan) => `Get ${plan}` }}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `plans` | `PricingPlan[]` |  | The plans, in order. PricingPlan = { id; name; description?; price: number \| { monthly; yearly } \| null; priceSuffix?; badge?; featured?; current?; features?: PricingFeature[]; featuresTitle?; cta?: { label; href?; onClick?: () => void \| Promise<unknown> }; note?; disabled? }. price 0 is free, null is custom (the button defaults to labels.contact). PricingFeature = string \| { label; included?; hint? }. |
| `comparison?` | `PricingComparisonGroup[]` |  | The comparison below the plans, in groups: { label; rows: { label; hint?; values: Record<planId, boolean \| ReactNode> }[] }. true is a check, false a dash, anything else prints as text. |
| `defaultPeriod?` | `"monthly" \| "yearly"` | `"monthly"` | The billing period shown first while uncontrolled. |
| `period?` | `"monthly" \| "yearly"` |  | The billing period, controlled. Respected even with the toggle hidden. |
| `onPeriodChange?` | `(period: BillingPeriod) => void` |  | Fires when the toggle changes the period. |
| `showToggle?` | `boolean` | `true` | Shows the Monthly / Yearly switch. It hides by itself when no plan has a yearly price. |
| `yearlyDisplay?` | `"per-month" \| "per-year"` | `"per-month"` | How a yearly price reads: per-month shows the monthly equivalent with "Billed $90 yearly" under it; per-year shows the yearly amount with what it saves. |
| `showComparison?` | `boolean` | `true` | Shows the comparison table when comparison is given. |
| `featuredStyle?` | `"raised" \| "outline" \| "inverted"` | `"raised"` | How the featured plan stands out: raised floats higher and is 8px taller in the wide layout, outline draws a ring in the tone, inverted fills the card with the tone. |
| `columns?` | `number` | `0` | Plans side by side in the wide layout. 0 is the plan count up to 4. More plans than columns scroll sideways with a peek of the next one. |
| `mobileLayout?` | `"carousel" \| "stack"` | `"carousel"` | Below 768px of width: swipeable cards that start at the featured plan, with dots, or the cards stacked one under the other. |
| `animatePrice?` | `boolean` | `true` | Prices roll their digits to the new value on a period change. Reduced motion shows the new value at once. |
| `size?` | `"sm" \| "md" \| "lg"` | `"md"` | Card padding, type, price size and button height (sm: 40px buttons, md and lg: 48px). |
| `radius?` | `"md" \| "lg"` | `"lg"` | Plan card corners: md 16px, lg 24px. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The featured plan's badge, button, ring or fill, and the feature checks (neutral keeps them in the text color, other tones turn them green). |
| `color?` | `string` | `""` | Any CSS color for the featured plan. Replaces the tone; text on it turns black or white by lightness. |
| `savingsTone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"success"` | The tone of the "-20 %" badge in the yearly option (a saving is a status, so it is green by default). |
| `currency?` | `string` | `"USD"` | ISO 4217 code for every price: "$", "€", "lei". |
| `locale?` | `string` | `""` | Intl locale for prices and the savings badge ("$49", "12 €"). Empty uses the UIProvider locale, then the page. |
| `labels?` | `Partial<PricingTableLabels>` |  | Every string, English by default (DEFAULT_PRICING_TABLE_LABELS): monthly, yearly, save (percent, formatted), perMonth "/mo", perYear "/yr", perMonthSpoken, perYearSpoken, billedYearly (amount), yearlySaving (amount), free, custom, contact, current, popular, included, notIncluded, compare, choose (plan), hint, period, plans, feature, slide (index, total) and showPlan (plan) for the phone carousel. |
| `classNames?` | `Partial<Record<"header" \| "toggle" \| "plans" \| "plan" \| "price" \| "cta" \| "features" \| "comparison", string>>` |  | Extra classes for the inner parts. |
| `className?` | `string` |  | Extra classes for the root, merged last. |

## Parts

### DEFAULT_PRICING_TABLE_LABELS (constant)

The English strings: the period switch, "Save 20%", per month and per year, free, custom, "Most popular", "Current plan", the comparison table and the carousel. Override keys through labels.

### PricingPlan (type)

One plan: { id, name, description?, price (a number, { monthly, yearly }, 0 for free or null for custom), priceSuffix?, badge?, featured?, current?, features?, featuresTitle?, cta?, note?, disabled? }.

### PricingComparisonGroup (type)

A section of the comparison table: { label, rows }, each row { label, hint?, values } with one value per plan id (true / false for a check or a dash).

### PricingTableProps (type)

The props of PricingTable, for wrappers that load plans from your billing provider.

## Examples

- **Brand, inverted featured plan**: tone="brand" with featuredStyle="inverted": the one plan you want sold, filled with the brand color.
- **Current plan**: Pro is the seller's plan (a check instead of a button); the others offer to switch.
- **Custom plan**: price: null prints the custom label and the button defaults to "Contact sales".
- **SaaS in euro (en-US)**: Acme CRM per user: decimals where the yearly price needs them, the default labels.
- **Two plans, stacked**: mobileLayout="stack" and radius="md": the cards stack on a phone instead of swiping.
- **Comparison on a phone**: Below 768px of width the comparison is a plan switcher above one column of rows; tap (i) for a hint.

## See also

- [Price](https://wingo-ui.com/components/price) ([Markdown](https://wingo-ui.com/components/price.md))
- [Cart drawer](https://wingo-ui.com/components/cart-drawer) ([Markdown](https://wingo-ui.com/components/cart-drawer.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Table](https://wingo-ui.com/components/table) ([Markdown](https://wingo-ui.com/components/table.md))
- [Carousel](https://wingo-ui.com/components/carousel) ([Markdown](https://wingo-ui.com/components/carousel.md))

---

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