Cancel Readback

Reads back exactly when access ends, what is lost and what is refunded before someone cancels, and ends on a receipt with an undo.

StopsCancelled without knowing when access ends

Installation

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

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

Usage

cancel-readback-usage.tsx
import { CancelReadback } from "@/components/ui/cancel-readback"

<CancelReadback
  plan="Pro"
  accessEndsAt={endsAt}
  locale="en-IN"
  timeZone="Asia/Kolkata"
  lose={["Unlimited projects", "Custom domains", "Priority support"]}
  keep={["All your data, read-only", "3 projects on Free"]}
  alternatives={ALTERNATIVES}
  onAlternative={(id) => setNote(id === "pause" ? "You chose to pause for 1 month." : "You chose to switch to Free.")}
  onConfirm={() => new Promise<void>((resolve) => setTimeout(resolve, 600))}
  onResume={() => new Promise<void>((resolve) => setTimeout(resolve, 400))}
  onKeep={() => setNote("You kept Pro. Nothing changed.")}
/>

What it catches

  • Cancelling without knowing when access actually ends (date, time, zone and days left)
  • Unclear refunds, by stating the refund or that there is none
  • Dark-pattern retention: alternatives appear once, below a cancel button that stays put
  • Regret, with a resume action on the receipt

Props

CancelReadback props
PropTypeDefault
plan* (required)

Plan name, e.g. "Pro".

string—
accessEndsAt* (required)

When paid access ends if they cancel now.

Date | number | string—
lose* (required)

What the user loses when access ends.

string[]—
keep* (required)

What the user keeps after cancelling.

string[]—
refund

Refund wording, e.g. "₹1,240 back to your UPI within 5–7 days". `null` means no refund.

string | nullnull
alternatives

Pause or downgrade options, offered once, below the cancel button.

CancelAlternative[][]
onAlternative

Called with the id of a pause or downgrade option the user picks.

(id: string) => void—
onConfirm* (required)

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

() => void | Promise<void>—
onResume

Undoes the cancellation while access is still active.

() => void | Promise<void>—
onKeep

Called when the user decides to keep the plan.

() => void—
cancelled

Controlled cancelled state.

boolean—
defaultCancelled

Initial cancelled state when uncontrolled.

booleanfalse
onCancelledChange

Called when the plan is cancelled or resumed.

(cancelled: boolean) => void—
locale

BCP 47 locale for the end date.

string—
timeZone

IANA time zone for the end date. Defaults to the visitor's.

string—
title

Heading; receives the plan name.

(plan: string) => React.ReactNode(p) => `Cancel ${p}`
confirmLabel

Cancel button text; receives the plan and end date.

(plan: string, end: End) => string(p, end) => (end.endsNow ? `Cancel ${p} now` : `Cancel, k…
keepLabel

Keep-plan button text.

(plan: string) => string(p) => `Keep ${p}`
resumeLabel

Resume button text on the receipt.

string"Undo cancellation"
noRefundText

Shown when there is no refund.

(end: End) => string(end) => (end.endsNow ? "No refund." : `No refund: you've…
earlyText

Explains that access continues until the end date.

(plan: string, end: End) => string(p, end) => `Cancelling today doesn't cut your time short…
doneText

Receipt text after cancelling.

(plan: string, end: End) => string(p, end) => (end.endsNow ? `${p} is cancelled and has end…
alternativesLabel

Heading for pause and downgrade options.

string"Or, instead of cancelling"
hideLabel

Text for hiding the alternatives.

string"Hide"

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

Accessibility

  • Labelled by its title (aria-labelledby)
  • The receipt is announced with role="status"
  • Buttons set aria-busy while pending
  • Lost and kept items use icons plus text

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: Cancel and resume on the server and compute access end and refunds from billing records, not from what the screen showed.

  • Shows the access end, refund and items you pass; it does not compute them
  • Cancelling and resuming happen in your callbacks

What it stops

Cancelled without knowing when access ends

Also known as

cancel subscription · unsubscribe · billing · pause plan · downgrade · refund · churn · manage plan

Dependencies

Pairs well with