Pricing Table
A pricing table with a monthly/yearly segmented switch; prices spring-tween on switch, the selected pill slides, the featured plan scales up and is highlighted, and yearly shows a discount badge.
npx shadcn@latest add https://webberui.com/r/pricing-table.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<PricingTable />
Installation
npx shadcn@latest add https://webberui.com/r/pricing-table.jsonOr, once registries are configured in components.json, install it as @webberui/pricing-table.
Usage
import { PricingTable, type PricingPlan } from "@/components/ui/pricing-table";
const plans: PricingPlan[] = [
{
name: "Free",
monthly: 0,
yearly: 0,
features: ["1 user", "Basic analytics", "Community support"],
cta: "Start for free",
},
{
name: "Pro",
monthly: 12,
yearly: 115,
features: ["5 users", "Advanced analytics", "Email support"],
highlighted: true,
cta: "Upgrade to Pro",
},
{
name: "Team",
monthly: 32,
yearly: 300,
features: ["Unlimited users", "Full analytics", "Priority support"],
cta: "Contact us",
},
];
<PricingTable plans={plans} currency="$" />;The billing period switch is managed by the component internally — just fill monthly and yearly with the actual amount for each period. The discount badge is computed automatically from how much yearly saves relative to "monthly × 12", and only appears in yearly mode when there is a saving. The highlighted plan scales up, gets a highlighted border, and carries a badge.
Props
PricingTable
| Prop | Type | Default | Description |
|---|---|---|---|
plans | PricingPlan[] | — | The plans, shown side by side |
currency | string | "$" | Currency symbol, placed before the price |
className | string | — | Class appended to the outermost container |
PricingPlan
| Field | Type | Description |
|---|---|---|
name | string | Plan name |
monthly | number | Monthly price (amount per month) |
yearly | number | Yearly price (amount per year), used to compute the discount |
features | string[] | Feature list; each row is prefixed with a Check icon |
highlighted | boolean | Mark as the featured plan: the card scales up, the border is highlighted, and a badge is shown (optional) |
cta | string | CTA button text; defaults to 「開始使用」 (Get started) when not specified (optional) |
How it works
- Period switch: monthly/yearly is a segmented control whose selected pill spring-slides between the two positions using Motion's
layoutIdshared layout, rather than fading out and repainting - Price spring tween: when the period changes, each card's price is written straight into the DOM through a MotionValue (without triggering a React re-render) and springs to the new value, displayed with thousands separators and rounded to a whole number
- Discount badge: in yearly mode, if the yearly price is lower than "monthly × 12", a 「年繳省 N%」 (Save N% yearly) badge fades in under the price via
AnimatePresence; the badge sits in a fixed-height slot so its appearance and disappearance never push the layout around - Featured plan: at
smand above, thehighlightedcard scales up (scale-[1.03]), gets a thicker highlighted border, is raised inz-index, and shows a 「推薦」 (Recommended) badge at the top, with the CTA turned into a solid button - Equal-height side by side: the cards sit side by side in a grid and stack into a single column automatically below
sm
Accessibility
- When the user has "reduce motion" enabled at the system level, the switch pill drops
layoutId(it simply appears at the new position), prices show their final value directly, and the discount badge does not fade in or out (the first render matches SSR, and the static behaviour only kicks in after mount) - The switch is a
role="group"with anaria-label, and the two options mark the current period witharia-pressed - The price block carries an
aria-label(with currency, amount, and period), and the rolling digits mid-tween are hidden from assistive technology (aria-hidden), so screen readers announce only the final value - The Check icons prefixing the features are decorative and marked
aria-hidden; the features are presented as a semantic<ul>list
Testimonial Wall
A three-column testimonial wall whose columns scroll in alternating directions, in either an auto infinite-scroll or a scroll-parallax style.
Animated Charts
Data-driven SVG charts that grow as they reveal — bars grow up from the baseline, lines and areas draw themselves along their stroke — triggered when they enter the viewport.