Start
- Included: 5 active listings
- Not included: Promotions
- Included: Basic stats
- Included: Unlimited messages with buyers
Commerce
Pricing table
v1.0.0Added in 1.0.0
A React pricing table: plans side by side with a monthly / yearly switch whose prices roll, a featured plan and a full comparison table.
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.
Pricing table is a Pro React component in the React commerce components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add pricing-table, together with Badge, Button, Carousel, Price, Segmented Control and 8 more and the npm packages motion and lucide-react.
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add pricing-tablePricing table is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Import PricingTable from @/components/ui/pricing-table.
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}` }}
/>
)
}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? }.
comparisonPricingComparisonGroup[]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.
defaultPerioddefault "monthly""monthly" | "yearly"The billing period shown first while uncontrolled.
period"monthly" | "yearly"The billing period, controlled. Respected even with the toggle hidden.
onPeriodChange(period: BillingPeriod) => voidFires when the toggle changes the period.
showToggledefault truebooleanShows the Monthly / Yearly switch. It hides by itself when no plan has a yearly price.
yearlyDisplaydefault "per-month""per-month" | "per-year"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.
showComparisondefault truebooleanShows the comparison table when comparison is given.
featuredStyledefault "raised""raised" | "outline" | "inverted"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.
columnsdefault 0numberPlans 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.
mobileLayoutdefault "carousel""carousel" | "stack"Below 768px of width: swipeable cards that start at the featured plan, with dots, or the cards stacked one under the other.
animatePricedefault truebooleanPrices roll their digits to the new value on a period change. Reduced motion shows the new value at once.
sizedefault "md""sm" | "md" | "lg"Card padding, type, price size and button height (sm: 40px buttons, md and lg: 48px).
radiusdefault "lg""md" | "lg"Plan card corners: md 16px, lg 24px.
tonedefault "neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"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).
colordefault ""stringAny CSS color for the featured plan. Replaces the tone; text on it turns black or white by lightness.
savingsTonedefault "success""neutral" | "brand" | "success" | "warning" | "danger" | "info"The tone of the "-20 %" badge in the yearly option (a saving is a status, so it is green by default).
currencydefault "USD"stringISO 4217 code for every price: "$", "€", "lei".
localedefault ""stringIntl locale for prices and the savings badge ("$49", "12 €"). Empty uses the UIProvider locale, then the page.
labelsPartial<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.
classNamesPartial<Record<"header" | "toggle" | "plans" | "plan" | "price" | "cta" | "features" | "comparison", string>>Extra classes for the inner parts.
classNamestringExtra classes for the root, merged last.
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.
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? }.
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).
The props of PricingTable, for wrappers that load plans from your billing provider.
tone="brand" with featuredStyle="inverted": the one plan you want sold, filled with the brand color.
Pro is the seller's plan (a check instead of a button); the others offer to switch.
price: null prints the custom label and the button defaults to "Contact sales".
Acme CRM per user: decimals where the yearly price needs them, the default labels.
mobileLayout="stack" and radius="md": the cards stack on a phone instead of swiping.
Below 768px of width the comparison is a plan switcher above one column of rows; tap (i) for a hint.
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
Run npx wingo-ui@latest add pricing-table. It also adds the Wingo UI items it builds on (Badge, Button, Carousel, Price, Segmented Control and 8 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/pricing-table.
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.
Build it: screens and apps10 min read
Build it: screens and apps11 min read
Comparisons and alternatives11 min read
Build it: screens and apps10 min read
Post more listings, promote them to the top of their category and sell faster. Change or cancel any time.
14 days free, no card
Everything in Start, plus:
Everything in Pro, plus:
14 days free, no card
Everything in Start, plus:
Everything in Pro, plus:
period=monthly · chosen: nothing yet