KQ-1333 · CHECKOUT/FLOW
Checkout Flow
A four-step checkout beside a sticky order summary: a step bar whose pill slides to the current stop on snap, with reached stops drawing a check on flick, and steps that either swap by direction 16px on snap inside a box that glides to each panel's measured height, or stand as sections that fold to a summary line as you finish them. A Continue the step cannot take shakes its head on a short tween, never a spring; delivery choices move the selection on snap and roll the summary's figures, and the payment step draws a generic card where every typed digit drops into its slot on flick and an outline travels on snap to the part the focused field writes. Place order holds pending while the order is placed, then the button itself becomes the confirmation as a shared layout carries its box out on glide, a check draws and the order number lands on recoil; every step is a real form where Enter continues, delivery is a native radio group, and each new step's heading takes focus as it arrives.
step 1 of 4 · address
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/checkout-flow
Props
- items
- CheckoutItem[]
- default defaultCheckoutItems
- What is being bought: name, variant, unit price, quantity, and a thumbnail or drawn art.
- deliveryOptions
- CheckoutDeliveryOption[]
- default defaultDeliveryOptions
- How it can get there: label, working days from now, price, an optional note and verb.
- value / defaultValue
- CheckoutValues
- default — / defaultCheckoutValues
- Every answer: the address, the delivery id, the card (digits only) and whether billing matches delivery.
- onValueChange
- (values: CheckoutValues) => void
- default —
- Fires from the keystroke or choice that changed an answer, with every answer.
- step / defaultStep
- number
- default — / 0
- Controlled or initial step: 0 address, 1 delivery, 2 payment, 3 review.
- onStepChange
- (step: number) => void
- default —
- Fires from Continue, Back, Edit or the step bar with the step asked for.
- onInvalid
- (step: number, fields: string[]) => void
- default —
- A Continue (or Place order) was held, with the step and the fields that need attention.
- onPlaceOrder
- (values: CheckoutValues, total: number) => void | Promise<{ orderId?: string } | void>
- default —
- Place order was pressed. A promise holds it pending; resolve with an order id to show it, reject to show why.
- state
- "idle" | "pending" | "confirmed" | "error"
- default —
- Controlled place-order state.
- onStateChange
- (state: CheckoutState) => void
- default —
- Fires as the order goes pending, confirmed or refused.
- onContinueShopping
- () => void
- default —
- Continue shopping was pressed on the confirmation.
- now
- Date | number
- default 2 Oct 2026, 09:30 UTC
- The moment delivery dates and card expiry count from; never read from the clock during render.
- steps
- "slide" | "stack"
- default "slide"
- One step at a time sliding by direction, or every step stacked as sections that fold.
- summary
- "side" | "bar" | "off"
- default "side"
- The order summary as a sticky column beside the form (a bar on phones), a collapsible bar at every width, or only on the review.
- format
- "groups" | "mask" | "plain"
- default "groups"
- How the card number formats as it is typed: spaced groups with the caret kept, a guide whose bullets fill in place, or plain digits.
- formatPrice
- (amount: number) => string
- default euros
- Formats an amount for display.
- testCard
- CheckoutCard
- default —
- A test card the payment step offers to fill in one press, for sandbox checkouts.
- countries
- string[]
- default defaultCountries
- The countries offered for delivery.
- title
- string
- default "Checkout"
- The heading and accessible name.
- placeLabel
- string
- default "Place order"
- The final button's words; the total follows them.
- pendingLabel
- string
- default "Placing order…"
- The final button while the order is placed; the button keeps its width.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading draws placeholders; error offers Retry.
- onRetry
- () => void
- default —
- The Retry button of the error state.
- label
- string
- default —
- The region's accessible name when it should differ from the title.
- sound
- boolean
- default false
- Clicks on step changes, choices and Edit, a low click on a refused Continue, a chime when the order is confirmed.
- disabled
- boolean
- default false
- Shows the checkout but takes no input.
- className
- string
- default —
- Classes for the region; give it a max height and it scrolls inside itself with the summary pinned.
| Prop | Type | Default | Description |
|---|---|---|---|
| items | CheckoutItem[] | defaultCheckoutItems | What is being bought: name, variant, unit price, quantity, and a thumbnail or drawn art. |
| deliveryOptions | CheckoutDeliveryOption[] | defaultDeliveryOptions | How it can get there: label, working days from now, price, an optional note and verb. |
| value / defaultValue | CheckoutValues | — / defaultCheckoutValues | Every answer: the address, the delivery id, the card (digits only) and whether billing matches delivery. |
| onValueChange | (values: CheckoutValues) => void | — | Fires from the keystroke or choice that changed an answer, with every answer. |
| step / defaultStep | number | — / 0 | Controlled or initial step: 0 address, 1 delivery, 2 payment, 3 review. |
| onStepChange | (step: number) => void | — | Fires from Continue, Back, Edit or the step bar with the step asked for. |
| onInvalid | (step: number, fields: string[]) => void | — | A Continue (or Place order) was held, with the step and the fields that need attention. |
| onPlaceOrder | (values: CheckoutValues, total: number) => void | Promise<{ orderId?: string } | void> | — | Place order was pressed. A promise holds it pending; resolve with an order id to show it, reject to show why. |
| state | "idle" | "pending" | "confirmed" | "error" | — | Controlled place-order state. |
| onStateChange | (state: CheckoutState) => void | — | Fires as the order goes pending, confirmed or refused. |
| onContinueShopping | () => void | — | Continue shopping was pressed on the confirmation. |
| now | Date | number | 2 Oct 2026, 09:30 UTC | The moment delivery dates and card expiry count from; never read from the clock during render. |
| steps | "slide" | "stack" | "slide" | One step at a time sliding by direction, or every step stacked as sections that fold. |
| summary | "side" | "bar" | "off" | "side" | The order summary as a sticky column beside the form (a bar on phones), a collapsible bar at every width, or only on the review. |
| format | "groups" | "mask" | "plain" | "groups" | How the card number formats as it is typed: spaced groups with the caret kept, a guide whose bullets fill in place, or plain digits. |
| formatPrice | (amount: number) => string | euros | Formats an amount for display. |
| testCard | CheckoutCard | — | A test card the payment step offers to fill in one press, for sandbox checkouts. |
| countries | string[] | defaultCountries | The countries offered for delivery. |
| title | string | "Checkout" | The heading and accessible name. |
| placeLabel | string | "Place order" | The final button's words; the total follows them. |
| pendingLabel | string | "Placing order…" | The final button while the order is placed; the button keeps its width. |
| status | "ready" | "loading" | "error" | "ready" | Loading draws placeholders; error offers Retry. |
| onRetry | () => void | — | The Retry button of the error state. |
| label | string | — | The region's accessible name when it should differ from the title. |
| sound | boolean | false | Clicks on step changes, choices and Edit, a low click on a refused Continue, a chime when the order is confirmed. |
| disabled | boolean | false | Shows the checkout but takes no input. |
| className | string | — | Classes for the region; give it a max height and it scrolls inside itself with the summary pinned. |