WebberUI

Installment Calculator (React)

An inline installment calculator React component for product pages: 3/6/12/24-month segmented switch, annual rate and fees, a rolling monthly amount, total and interest, effective annual rate, a 0% badge and an expandable per-period schedule.

The "as low as NT$XXX per month" line you see on every Taiwanese e-commerce product page. Pass in the product price and a list of plans (months, annual rate, fee) and the component computes the monthly payment with the standard amortization formula and shows it as a digit-by-digit rolling number. Switching the term slides the selected pill across, the total / interest + fees / effective annual rate update in step, and zero-interest plans automatically get a "0 利率" (0% interest) badge. Expand the schedule if you want to see principal, interest and remaining balance for every period. The computeInstallment helper is exported separately, so a product listing can call it directly to print "as low as … per month".

Loading preview…
npx shadcn@latest add https://webberui.com/r/installment-calculator.json

Playground

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

36900
3000
<InstallmentCalculator />

Installation

npx shadcn@latest add https://webberui.com/r/installment-calculator.json

Or, once registries are configured in components.json, install it as @webberui/installment-calculator.

Usage

import {
  InstallmentCalculator,
  computeInstallment,
} from "@/components/ui/installment-calculator";

<InstallmentCalculator
  price={36900}
  plans={[
    { months: 3, annualRate: 0 },
    { months: 6, annualRate: 0, badge: "熱門" },
    { months: 12, annualRate: 4.99 },
    { months: 24, annualRate: 7.99, fee: 300 },
  ]}
  defaultMonths={6}
  minAmount={3000}
  onChange={(plan, result) =>
    console.log(`${plan.months} months, NT$${result.monthly} per month`)
  }
/>

// The helper can be used on its own (e.g. "as low as … per month" in a product listing)
const { monthly, effectiveAnnualRate } = computeInstallment(36900, 12, 4.99);

Props

PropTypeDefaultDescription
pricenumberProduct price
plansInstallmentPlan[]3 / 6 / 12 / 24 monthsList of installment plans; the defaults are 3 and 6 months at 0%, 12 months at 4.5% and 24 months at 6.5%
valuenumberControlled: the currently selected term in months (must exist in plans)
defaultMonthsnumberfirst planUncontrolled: the initially selected term; falls back to the first plan when it is not in plans
onChange(plan: InstallmentPlan, result: InstallmentResult) => voidCallback when the user switches terms, with the selected plan and its computed result
currencystring"NT$"Currency prefix
showSchedulebooleantrueWhether to offer the expandable per-period schedule
minAmountnumber0Minimum amount eligible for installments; when price is below it a notice is shown instead and the plan switch is disabled
disclaimerstring"實際費率以發卡銀行為準" (actual rates are set by the issuing bank)Small-print disclaimer at the bottom; pass an empty string to hide it
classNamestringForwarded to the outermost container

InstallmentPlan

FieldTypeDefaultDescription
monthsnumberTerm in months
annualRatenumberAnnual interest rate (%); 0 means zero interest
feenumber0One-off handling fee
labelstring"N 期" (N months)Text on the segmented button
badgestringSmall badge text at the top-right of the segmented button, e.g. "熱門" (popular)

InstallmentResult

FieldTypeDefaultDescription
monthsnumberTerm in months
annualRatenumberAnnual interest rate (%)
feenumberHandling fee
monthlynumberMonthly payment (rounded to the dollar; the last period may differ slightly because it absorbs the rounding remainder)
totalnumberTotal paid (sum of all payments + fee)
interestnumberTotal interest
extraCostnumberInterest + fee
effectiveAnnualRatenumberEffective annual rate (%, fee included, two decimals)
zeroInterestbooleanWhether the plan is zero-interest
scheduleInstallmentScheduleRow[]Per-period schedule

InstallmentScheduleRow

FieldTypeDefaultDescription
periodnumberPeriod number (starting at 1)
paymentnumberAmount due this period (principal + interest)
principalnumberPrincipal repaid this period
interestnumberInterest for this period
balancenumberRemaining principal after this period

computeInstallment(price, months, annualRate, fee?) is also a named export returning an InstallmentResult, so the same math can be reused in listings, the cart or checkout.

How it works

  • Formula: monthly rate r = annualRate / 12 / 100; when r is 0 the monthly payment is price / n, otherwise the standard amortized payment price × r × (1 + r)^n / ((1 + r)^n − 1). Amounts are rounded to the dollar.
  • Schedule: interest for each period = remaining principal × r, principal = monthly payment − interest; the last period clears whatever principal is left and absorbs the rounding remainder, so the principal parts add up exactly to the product price.
  • Effective annual rate: the fee is treated as a cost paid at the moment of borrowing (net financed amount = price − fee), the monthly internal rate of return is solved by bisection and multiplied by 12 — with no fee it equals the nominal annual rate; a zero-interest plan with a fee shows an effective rate above 0, so the "0 利率" badge cannot mislead.
  • Rolling digits: each digit is a vertical 0–9 wheel that translateYs to its target; keys are assigned from the right, so the ones digit keeps its wheel when the digit count changes and new digits fade in from the left. The first render outputs the final position directly, so hydration does not roll up from 0.
  • Threshold: when price is below minAmount, the result card is replaced by a "below the installment threshold" notice and the plan switch is disabled.

Accessibility

  • The term switch uses role="radiogroup" with role="radio" / aria-checked; Tab lands only on the selected item, arrow keys and Home / End move between plans and select them, and focus is shown with a focus-visible ring
  • The rolling-digit layer of the monthly amount is aria-hidden and the wrapper carries the full amount as aria-label; a hidden aria-live="polite" one-line summary (term, monthly payment, total) is read out on switch, instead of turning the whole result card into a noisy live region
  • The schedule toggle carries aria-expanded / aria-controls; the schedule is a native table with a caption and scope-marked column and row headers
  • When the user has "reduce motion" enabled at the system level, the wheels become static digits and the pill slide, badge pop, schedule expansion and value transitions all snap into place, leaving only the color and text feedback

On this page