KQ-1703 · PAUSE/INSTEAD
Pause Instead
A cancellation flow built from plan, usage and offers: the reason, what changes, then a plain choice, in a step bar whose pill slides on springs.snap while each step cross-slides 8px on springs.glide inside a box that glides to its measured height. When "What changes" comes into view the losses switch off one at a time inside the 600ms budget — a mini switch flips on snap, a strike sweeps on glide, or a tile's live bar drains — and the pause offer slides up 16px beneath on glide with a resume date that rolls as the length changes. Reasons and pause lengths are radiogroups (arrows, Home, End), every step and decision is a real button, and a step change moves focus to its heading.
Fieldline · Billing
Cancel Team
8 seats · $12 a seat a month · paid through 12 Nov 2026 (23 days left)
- Reason
- What changes
- Decide
step 1 of 3 · reason · 3 things switch off
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/pause-instead
Props
- plan
- PauseInsteadPlan
- default defaultPauseInsteadPlan
- The plan being cancelled: { id, product, name, price, seats, cycle, renewsAt, currency? }.
- usage
- PauseInsteadLoss[]
- default defaultPauseInsteadUsage
- What switches off when the plan ends, in order: { id, label, detail, figure?, icon? }.
- keeps
- PauseInsteadKeep[]
- default defaultPauseInsteadKeeps
- What stays whatever is chosen: { id, label }.
- reasons
- PauseInsteadReason[]
- default defaultPauseInsteadReasons
- The reasons offered on the first step: { id, label }.
- offers
- PauseInsteadOffers
- default defaultPauseInsteadOffers
- { pause?: { months, defaultMonths? }, downgrade?: { plan, price, keeps } }; omit one to leave it out.
- now
- number
- default plan.renewsAt − 23 days
- The moment "days left" counts from, in ms.
- step / defaultStep
- "reason" | "losses" | "decide" | "done"
- default — / "reason"
- The step shown, controlled or initial.
- onStepChange
- (step) => void
- default —
- Fires from the button that moved the flow.
- onCancel
- (details: { reason, note }) => void | Promise
- default —
- Cancel was confirmed; a promise holds the button and a rejection shows its message.
- onPause
- (details: { months, resumesAt, reason, note }) => void | Promise
- default —
- A pause was chosen, with its length and the date billing resumes.
- onDowngrade
- (details: { plan, reason, note }) => void | Promise
- default —
- The cheaper plan was chosen.
- onExit
- (outcome: "kept" | "paused" | "downgraded" | "cancelled") => void
- default —
- The flow was left: Keep my plan, or Done after an outcome.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the plan's data has arrived.
- onRetry
- () => void
- default —
- Try again was pressed after the plan failed to load.
- listStyle
- "switch" | "strike" | "tiles"
- default "switch"
- How the losses are drawn: rows with a switch that flips off, rows struck through, or figure tiles that drain.
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Padding, gaps and type size.
- tone
- "calm" | "warm" | "mono"
- default "calm"
- The offer's wash, the primary fill and the step pill: cobalt, amber pigment or ink.
- dimInterval
- number
- default 150
- Milliseconds between one loss switching off and the next; the run never passes 600ms.
- formatDate
- (ms) => string
- default "12 Nov 2026" (UTC)
- How a date is written.
- formatMoney
- (amount, currency) => string
- default Intl currency
- How a price is written.
- maxHeight
- number
- default 560
- The surface's height cap in px; the steps scroll inside it with edge fades.
- disabled
- boolean
- default false
- Every action is unavailable; the steps can still be read.
- className
- string
- default —
- Classes for the surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| plan | PauseInsteadPlan | defaultPauseInsteadPlan | The plan being cancelled: { id, product, name, price, seats, cycle, renewsAt, currency? }. |
| usage | PauseInsteadLoss[] | defaultPauseInsteadUsage | What switches off when the plan ends, in order: { id, label, detail, figure?, icon? }. |
| keeps | PauseInsteadKeep[] | defaultPauseInsteadKeeps | What stays whatever is chosen: { id, label }. |
| reasons | PauseInsteadReason[] | defaultPauseInsteadReasons | The reasons offered on the first step: { id, label }. |
| offers | PauseInsteadOffers | defaultPauseInsteadOffers | { pause?: { months, defaultMonths? }, downgrade?: { plan, price, keeps } }; omit one to leave it out. |
| now | number | plan.renewsAt − 23 days | The moment "days left" counts from, in ms. |
| step / defaultStep | "reason" | "losses" | "decide" | "done" | — / "reason" | The step shown, controlled or initial. |
| onStepChange | (step) => void | — | Fires from the button that moved the flow. |
| onCancel | (details: { reason, note }) => void | Promise | — | Cancel was confirmed; a promise holds the button and a rejection shows its message. |
| onPause | (details: { months, resumesAt, reason, note }) => void | Promise | — | A pause was chosen, with its length and the date billing resumes. |
| onDowngrade | (details: { plan, reason, note }) => void | Promise | — | The cheaper plan was chosen. |
| onExit | (outcome: "kept" | "paused" | "downgraded" | "cancelled") => void | — | The flow was left: Keep my plan, or Done after an outcome. |
| status | "ready" | "loading" | "error" | "ready" | Whether the plan's data has arrived. |
| onRetry | () => void | — | Try again was pressed after the plan failed to load. |
| listStyle | "switch" | "strike" | "tiles" | "switch" | How the losses are drawn: rows with a switch that flips off, rows struck through, or figure tiles that drain. |
| density | "compact" | "cozy" | "roomy" | "cozy" | Padding, gaps and type size. |
| tone | "calm" | "warm" | "mono" | "calm" | The offer's wash, the primary fill and the step pill: cobalt, amber pigment or ink. |
| dimInterval | number | 150 | Milliseconds between one loss switching off and the next; the run never passes 600ms. |
| formatDate | (ms) => string | "12 Nov 2026" (UTC) | How a date is written. |
| formatMoney | (amount, currency) => string | Intl currency | How a price is written. |
| maxHeight | number | 560 | The surface's height cap in px; the steps scroll inside it with edge fades. |
| disabled | boolean | false | Every action is unavailable; the steps can still be read. |
| className | string | — | Classes for the surface. |