Data Display

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.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add pricing-table.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

Active edited usageDefault props
<PricingTable/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/pricing-table.en.json

Or, 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

PropTypeDefaultDescription
plansPricingPlan[]—The plans, shown side by side
currencystring"$"Currency symbol, placed before the price
classNamestring—Class appended to the outermost container

PricingPlan

FieldTypeDescription
namestringPlan name
monthlynumberMonthly price (amount per month)
yearlynumberYearly price (amount per year), used to compute the discount
featuresstring[]Feature list; each row is prefixed with a Check icon
highlightedbooleanMark as the featured plan: the card scales up, the border is highlighted, and a badge is shown (optional)
ctastringCTA button text; defaults to “Get started” (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 layoutId shared 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 “Annual Payment Savings 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 sm and above, the highlighted card scales up (scale-[1.03]), gets a thicker highlighted border, is raised in z-index, and shows a “Recommendations” (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 an aria-label, and the two options mark the current period with aria-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