Plan Change Readback

Before an upgrade or downgrade, reads back today's charge, credits, the next bill and any usage over the new plan's limits, so nobody is surprised by a charge or lost data.

StopsDowngrade deleted data over the limit

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/plan-change-readback.json

The files land in your project. They're yours to edit.

Usage

plan-change-readback-usage.tsx
import { PlanChangeReadback } from "@/components/ui/plan-change-readback"

<PlanChangeReadback
  current={{ name: "Team", price: 1999, interval: "month" }}
  next={{ name: "Starter", price: 499, interval: "month" }}
  periodStart="2026-10-01T00:00:00+05:30"
  periodEnd="2026-11-01T00:00:00+05:30"
  now="2026-10-13T10:00:00+05:30"
  timeZone="Asia/Kolkata"
  defaultTiming="now"
  lose={["SSO and audit log", "Priority support"]}
  limits={[
    { label: "Seats", used: 8, limit: 3, effect: "The 5 most recently added members lose access.", destructive: true },
    { label: "Storage", used: 14, limit: 10, unit: "GB", effect: "Uploads pause until you're under; nothing is deleted." },
  ]}
  onConfirm={() => new Promise((resolve) => setTimeout(resolve, 900))}
/>

What it catches

  • Charge today and credit for unused time, prorated by day
  • Downgrade credit going to future bills rather than the card
  • Switching now vs at period end, side by side
  • Features lost on the new plan
  • Usage over the new plan's limits, with an acknowledgement when excess is removed

Props

PlanChangeReadback props
PropTypeDefault
current* (required)

The plan the account is on now.

PlanInfo—
next* (required)

The plan being switched to.

PlanInfo—
periodStart* (required)

The current billing period.

Date | string—
periodEnd* (required)

End of the current billing period.

Date | string—
now

The moment of the change. Pass it for deterministic rendering; otherwise the clock is read after mount.

Date | string—
timing

Controlled timing.

PlanChangeTiming—
defaultTiming

Initial timing. Defaults to now for upgrades and period end for downgrades, so paid-for features aren't cut short.

PlanChangeTiming—
onTimingChange

Called when the user picks now or at period end.

(timing: PlanChangeTiming) => void—
allowTimingChoice

Offer the now / period-end choice.

booleantrue
lose

Features the new plan doesn't have.

string[][]
gain

Features the new plan adds.

string[][]
limits

Limits the account already exceeds on the new plan.

PlanLimit[][]
currency

ISO 4217 currency of the prices.

string"INR"
locale

BCP 47 locale for money and dates.

string"en-IN"
timeZone

IANA zone for dates shown.

string—
onConfirm

Makes the change. Return a promise to show a pending state; a rejection keeps the form.

(timing: PlanChangeTiming) => void | Promise<void>—
carryText

Text for credit carried to future bills.

(amount: string, date: string) => string(amount, date) => `${amount} becomes credit on your bills…
acknowledgeText

Checkbox text acknowledging lost excess usage.

(limit: PlanLimit, over: string) => string(limit, over) => `I understand what happens to the ${over…
messages

Override any other user-visible string (see `PLAN_CHANGE_MESSAGES`).

Partial<PlanChangeMessages>—

* required. Also accepts the native props of <section> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Section labelled by its title; timing choice is a labelled radiogroup
  • Gained and lost features are marked with text as well as icons
  • The arrow between plans has an accessible label

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Get the real proration preview from your billing provider and apply limits on the server; require the acknowledgement server-side for destructive downgrades.

  • Day-based proration is an estimate; your billing provider's numbers are the real ones
  • Taxes are not included (prices before tax)
  • Doesn't change the plan itself; onConfirm does

What it stops

Downgrade deleted data over the limit

Also known as

upgrade · downgrade · proration · billing · plan · subscription · pricing · usage limits

Field type

Dependencies

Pairs well with