KQ-1323 · UPGRADE/WALL
Upgrade Wall
A paywall whose comparison shows what the chosen plan unlocks: those rows light in a cascade and their locks lift open on snap, and a plan change re-lights only the rows that changed while the chosen column's cells roll and, in the all-plans view, a band slides across to the new column on snap. Plan and billing are radiogroups with pills that slide on snap, every price rolls digit by digit, and the trial is drawn as a track from today to the first charge whose span sweeps in on glide, ticking off each day, with the reminder above it. The purchase button holds its width through pending, success and error — success stamps its check on recoil and turns the track and the unlocked rows green — and the plan and billing groups take arrow keys while the comparison is a real table.
Cancel before 16 Oct and you won't be charged. We email you 2 days ahead.
team · yearly · €360 on 16 oct
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/upgrade-wall
Props
- timeline
- "track" | "list" | "none"
- default "track"
- The trial drawn as a track from today to the first charge, as a vertical list of the same three moments, or no trial at all (billed today).
- compare
- "table" | "unlocks" | "all"
- default "table"
- The current plan against the chosen one, only what the chosen plan unlocks, or every plan side by side with a band behind the chosen column.
- cycle
- "both" | "monthly" | "yearly"
- default "both"
- The billing cycles offered: both behind a switch with the yearly saving, or only one.
- plans
- UpgradePlan[]
- default defaultUpgradePlans
- The plans, the current one included: name, tagline, monthly and yearly per-seat price, current flag and badge.
- features
- UpgradeFeature[]
- default defaultUpgradeFeatures
- The comparison rows, each with a value per plan: included, not included, or a short amount.
- current
- string
- default the plan marked current
- The id of the plan the account is on now.
- plan / defaultPlan
- string
- default the first plan with a badge
- The chosen plan. Controlled or uncontrolled.
- onPlanChange
- (plan: string) => void
- default —
- Fires from the press or arrow key that chose a plan.
- billing / defaultBilling
- "monthly" | "yearly"
- default "yearly"
- The billing cycle when both are offered. Controlled or uncontrolled.
- onBillingChange
- (billing: UpgradeBilling) => void
- default —
- Fires from the press or arrow key that switched billing.
- seats
- number
- default 3
- Seats the per-seat price is multiplied by.
- trialDays
- number
- default 14
- Length of the free trial, in days; the track has a tick for each.
- reminderDays
- number
- default 2
- How many days before the first charge the reminder goes out.
- now
- Date | number
- default defaultUpgradeNow
- Today, for the dates on the timeline. Days are UTC days, so server and client print the same dates.
- format
- (amount: number) => string
- default euros through Intl.NumberFormat
- Prints every amount: prices, totals and the first charge.
- state
- "idle" | "pending" | "success" | "error"
- default —
- Controlled purchase state. Without it the wall follows the promise onPurchase returns.
- onStateChange
- (state: UpgradeState) => void
- default —
- Fires from the press that started a purchase, and again when it settles.
- onPurchase
- (selection: UpgradeSelection) => void | Promise<void>
- default —
- Starts the purchase with the plan, billing, seats, trial, amount and first-charge date. Return a promise to hold the button pending; reject with an Error to show its message.
- onDismiss
- () => void
- default —
- The Not now button in the corner. Hidden without it.
- title
- string
- default "Upgrade to keep your dashboards live"
- The heading and the region's accessible name.
- subtitle
- string
- default —
- The line under it: why the wall is here.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading draws placeholders; error offers Retry.
- onRetry
- () => void
- default —
- The Retry button of the error state.
- sound
- boolean
- default false
- Play a click on plan and billing changes and a chime when your purchase lands. Off unless asked for.
- disabled
- boolean
- default false
- Shows the wall but takes no input.
- label
- string
- default —
- The region's accessible name, when it should differ from the title.
- className
- string
- default —
- Classes for the root. It is h-[560px] by default; pass a height to change it — the columns scroll inside.
| Prop | Type | Default | Description |
|---|---|---|---|
| timeline | "track" | "list" | "none" | "track" | The trial drawn as a track from today to the first charge, as a vertical list of the same three moments, or no trial at all (billed today). |
| compare | "table" | "unlocks" | "all" | "table" | The current plan against the chosen one, only what the chosen plan unlocks, or every plan side by side with a band behind the chosen column. |
| cycle | "both" | "monthly" | "yearly" | "both" | The billing cycles offered: both behind a switch with the yearly saving, or only one. |
| plans | UpgradePlan[] | defaultUpgradePlans | The plans, the current one included: name, tagline, monthly and yearly per-seat price, current flag and badge. |
| features | UpgradeFeature[] | defaultUpgradeFeatures | The comparison rows, each with a value per plan: included, not included, or a short amount. |
| current | string | the plan marked current | The id of the plan the account is on now. |
| plan / defaultPlan | string | the first plan with a badge | The chosen plan. Controlled or uncontrolled. |
| onPlanChange | (plan: string) => void | — | Fires from the press or arrow key that chose a plan. |
| billing / defaultBilling | "monthly" | "yearly" | "yearly" | The billing cycle when both are offered. Controlled or uncontrolled. |
| onBillingChange | (billing: UpgradeBilling) => void | — | Fires from the press or arrow key that switched billing. |
| seats | number | 3 | Seats the per-seat price is multiplied by. |
| trialDays | number | 14 | Length of the free trial, in days; the track has a tick for each. |
| reminderDays | number | 2 | How many days before the first charge the reminder goes out. |
| now | Date | number | defaultUpgradeNow | Today, for the dates on the timeline. Days are UTC days, so server and client print the same dates. |
| format | (amount: number) => string | euros through Intl.NumberFormat | Prints every amount: prices, totals and the first charge. |
| state | "idle" | "pending" | "success" | "error" | — | Controlled purchase state. Without it the wall follows the promise onPurchase returns. |
| onStateChange | (state: UpgradeState) => void | — | Fires from the press that started a purchase, and again when it settles. |
| onPurchase | (selection: UpgradeSelection) => void | Promise<void> | — | Starts the purchase with the plan, billing, seats, trial, amount and first-charge date. Return a promise to hold the button pending; reject with an Error to show its message. |
| onDismiss | () => void | — | The Not now button in the corner. Hidden without it. |
| title | string | "Upgrade to keep your dashboards live" | The heading and the region's accessible name. |
| subtitle | string | — | The line under it: why the wall is here. |
| status | "ready" | "loading" | "error" | "ready" | Loading draws placeholders; error offers Retry. |
| onRetry | () => void | — | The Retry button of the error state. |
| sound | boolean | false | Play a click on plan and billing changes and a chime when your purchase lands. Off unless asked for. |
| disabled | boolean | false | Shows the wall but takes no input. |
| label | string | — | The region's accessible name, when it should differ from the title. |
| className | string | — | Classes for the root. It is h-[560px] by default; pass a height to change it — the columns scroll inside. |