Skip to content
WANCRAFT

B.99Cards & Containers

Pricing Toggle

Pricing cards with a monthly / yearly switch, digit-rolling prices, savings badges and Intl currency formatting.

NewReactMotionTailwindintermediate

Overview

Pricing Toggle renders a row of plan cards under a monthly / yearly switch. Flip the period and every price rolls digit by digit from the number it showed before — never from zero — while a “Save X%” badge springs in on each plan that is cheaper yearly. Prices are formatted with Intl.NumberFormat for any currency and locale, so “$29”, “29 €” and “RM 119” all roll correctly, and the yearly price can be shown as a monthly equivalent or as the yearly total. Cards stack on narrow containers without media queries, one plan can be highlighted, and screen readers hear real sentences (“$24 per month, billed yearly at $290”) through a polite live region instead of spinning digits.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/pricing-toggle.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/pricing-toggle.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/pricing-toggle.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { PricingToggle, type PricingPlan } from "@/components/ui/pricing-toggle";

export function PricingToggleExample() {
  const plans: PricingPlan[] = [
    { id: "starter", name: "Starter", monthly: 12, yearly: 120, features: ["3 projects", "Community support"], cta: "Start free" },
    { id: "pro", name: "Pro", monthly: 29, yearly: 290, features: ["Unlimited projects", "Priority support"], cta: "Go Pro", highlighted: true },
    { id: "team", name: "Team", monthly: 79, yearly: 790, features: ["Everything in Pro", "SSO and audit log"], cta: "Contact sales" },
  ];

  return (
    <PricingToggle
      plans={plans}
      onSelectPlan={(plan, billing) => console.log(plan.id, billing)}
    />
  );
}

03Props

PropTypeDefaultDescription
plansPricingPlan[]—{ id; name; description?; monthly; yearly; features: string[]; cta; href?; highlighted?; badge? }. monthly is per month, yearly is per year.
billing / defaultBilling"monthly" | "yearly"— / "monthly"Controlled or initial billing period.
onBillingChange(billing: BillingPeriod) => void—Called when the period changes.
currencystring"USD"ISO 4217 currency code for Intl.NumberFormat.
localestring"en-US"BCP 47 locale for formatting.
yearlyDisplay"perMonth" | "perYear""perMonth"With yearly billing, show yearly ÷ 12 (with the total underneath) or the yearly total.
rollDurationnumber600How long each digit takes to roll, in ms.
showSavingsbooleantrue“Save X%” badges on yearly plans and the best saving on the switch.
onSelectPlan(plan: PricingPlan, billing: BillingPeriod) => void—Called when a plan's button is pressed (plans with href render links instead).
monthlyLabel / yearlyLabelstring"Monthly" / "Yearly"Switch labels.
accentstring"#FF7448"Highlight border, buttons, badges and focus ring.
...propsHTMLAttributes<HTMLDivElement>—Forwarded to the root element.

04Features

  • 01Monthly / yearly switch with a sliding thumb
  • 02Digit-wise rolling prices that start from the previous value, never from zero
  • 03Per-plan “Save X%” badges that spring in for yearly billing
  • 04Intl.NumberFormat currency formatting for any currency and locale
  • 05Yearly price as a monthly equivalent or as the yearly total
  • 06Highlighted plan, feature lists, button or link calls to action
  • 07Cards stack automatically in narrow containers (auto-fit grid, no media queries)
motion · springmotion · tweenmotion · layoutinput · clickinput · keyboardinput · focus

05Accessibility

  • The billing switch is a role="radiogroup" named “Billing period” with two role="radio" options, roving tabindex and arrow keys.
  • Rolling digits are aria-hidden; each card carries a screen-reader sentence such as “$24.17 per month, billed yearly at $290”.
  • A polite live region announces every plan's new price after the period changes.
  • Each plan is an article labelled by its heading, inside a list.
  • With prefers-reduced-motion prices change instantly and badges fade without springing.

06Limitations

  • Savings are computed as 1 − yearly ÷ (monthly × 12) and rounded to whole percent.
  • Monthly equivalents are rounded to the currency's usual fraction digits.