KQ-1771 · CHANGE/PREVIEW
Change Preview
A plan-change preview with the plans as a radio group and the billing period drawn as a timeline beneath: the days used, and from the change date the credit for the unused plan and the charge for the new one. Choosing a plan slides the marker to the change date on springs.snap, then the credit and charge segments grow from it toward the period's end on springs.glide, the credit a beat ahead, while the amount due today rolls to its new figure; a cheaper plan that waits for the period's end gets a dashed marker there. Every position is a CSS variable written by a motion value, so the same values drive the horizontal timeline and the vertical one at phone widths; the radios take the arrow keys, Home and End.
Business · on team · due today $26.13
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/change-preview
Props
- currentPlan
- string
- default "team"
- The plan the account is on now.
- plans
- ChangePlan[]
- default defaultChangePlans
- The plans on offer, cheapest first: { id, name, price, blurb? }.
- period
- ChangePeriod
- default Oct 1 – Nov 1 2026
- The current billing period: { start, end }.
- now
- number
- default Oct 17 2026, 14:00 UTC
- The moment the preview is made, in ms.
- value / defaultValue
- string
- default — / "business"
- The chosen plan id, controlled or initial.
- onPreview
- (plan) => void | Promise
- default —
- Fires from the press or key that chose a plan; a returned promise shows Calculating…, its figures replace the computed ones and a rejection is stated.
- onConfirm
- (plan, quote) => void | Promise
- default —
- Confirm was pressed with the quote; a returned promise holds the button.
- onCancel
- () => void
- default —
- Keep was pressed: the choice goes back to the current plan.
- errors
- { credit?, charge?, tax? }
- default —
- Parts the host could not calculate, each with its reason; Confirm waits until they are known.
- taxRate
- number
- default 0
- Tax on what is due today, as a fraction.
- downgradeAt
- "period-end" | "now"
- default "period-end"
- When a cheaper plan starts.
- account
- string
- default "Fernworks"
- The account changing plans.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the plans have arrived.
- onRetry
- () => void
- default —
- Try again was pressed after the plans failed to load.
- timelineStyle
- "track" | "rail" | "days"
- default "track"
- How the billing period is drawn: thick lanes, hairline rails, or one cell per day.
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Padding, gaps and the plan cards' height.
- accent
- "cobalt" | "plum" | "mint" | "ink" | string
- default "cobalt"
- Selection, charge and Confirm: a named pigment or any CSS colour.
- formatMoney
- (amount) => string
- default Intl USD
- How money is written.
- disabled
- boolean
- default false
- Plans can be compared but not changed.
- className
- string
- default —
- Classes for the panel; it caps at 36rem tall and scrolls inside past that.
| Prop | Type | Default | Description |
|---|---|---|---|
| currentPlan | string | "team" | The plan the account is on now. |
| plans | ChangePlan[] | defaultChangePlans | The plans on offer, cheapest first: { id, name, price, blurb? }. |
| period | ChangePeriod | Oct 1 – Nov 1 2026 | The current billing period: { start, end }. |
| now | number | Oct 17 2026, 14:00 UTC | The moment the preview is made, in ms. |
| value / defaultValue | string | — / "business" | The chosen plan id, controlled or initial. |
| onPreview | (plan) => void | Promise | — | Fires from the press or key that chose a plan; a returned promise shows Calculating…, its figures replace the computed ones and a rejection is stated. |
| onConfirm | (plan, quote) => void | Promise | — | Confirm was pressed with the quote; a returned promise holds the button. |
| onCancel | () => void | — | Keep was pressed: the choice goes back to the current plan. |
| errors | { credit?, charge?, tax? } | — | Parts the host could not calculate, each with its reason; Confirm waits until they are known. |
| taxRate | number | 0 | Tax on what is due today, as a fraction. |
| downgradeAt | "period-end" | "now" | "period-end" | When a cheaper plan starts. |
| account | string | "Fernworks" | The account changing plans. |
| status | "ready" | "loading" | "error" | "ready" | Whether the plans have arrived. |
| onRetry | () => void | — | Try again was pressed after the plans failed to load. |
| timelineStyle | "track" | "rail" | "days" | "track" | How the billing period is drawn: thick lanes, hairline rails, or one cell per day. |
| density | "compact" | "cozy" | "roomy" | "cozy" | Padding, gaps and the plan cards' height. |
| accent | "cobalt" | "plum" | "mint" | "ink" | string | "cobalt" | Selection, charge and Confirm: a named pigment or any CSS colour. |
| formatMoney | (amount) => string | Intl USD | How money is written. |
| disabled | boolean | false | Plans can be compared but not changed. |
| className | string | — | Classes for the panel; it caps at 36rem tall and scrolls inside past that. |