Checkout blocks
The pages between the cart and the receipt: a one-page checkout, a stepped
checkout, a payment method picker and the order confirmation. Each block is one
.svelte file built from SvTextInput, SvCountryInput, SvPhoneInput,
SvMaskedInput, SvRadioGroup, SvStepper, SvCheckBox and SvButton, with
money kept in integer cents and formatted with Intl.NumberFormat.
Installation
Add the whole group, or one block by its name (shown under each demo):
See them first with npx @svgrid/ui try blocks/checkout. No project needed.
Split checkout
Contact email, shipping address, delivery speed and card fields on the left; a sticky order summary with items, subtotal, shipping, tax and total on the right. Pay checks every field, including a Luhn check on the card number and an expiry that is not in the past, then shows the button's loading state. Below 820 px the summary moves above the form and collapses to one row with the total.
npx @svgrid/ui add checkout-split
Stepped checkout
Shipping, Payment and Review behind an SvStepper. Continue validates only the
current step. Review lists the address, phone, delivery speed and card with an
Edit link per section; after an edit, the button reads "Save and review" and
returns straight to Review.
npx @svgrid/ui add checkout-steps
Payment methods
Four radio cards: Card, Wallet pay, Bank transfer and Invoice. Arrow keys move between them. The form for the chosen method appears below it: card fields, the phone that approves a wallet payment, an IBAN with its check digits verified, or company name, VAT number, country and invoice email for business accounts. Continue validates the chosen method only.
npx @svgrid/ui add checkout-payment-methods
Order confirmation
The thank-you page: a drawn check mark, the order number with a copy button, a delivery window counted in business days from today, the shipping address, what happens next and the paid summary, with Track order and Continue shopping actions.
npx @svgrid/ui add checkout-confirmation
Taking the payment
Card numbers should never reach your own server. The SvMaskedInput card fields
in these blocks show the layout; in production, mount your payment provider's
hosted fields in their place (the provider's script renders the inputs in its
own frame and hands you a token), or skip card fields entirely and redirect the
shopper to the provider's payment page. Then delete the card, expiry and
cvc checks from the block's validator, since the provider validates its own
fields.
The order itself is created in a SvelteKit form action. Compute the total there from the cart, not from anything the browser sent, charge the token, and send the shopper to the confirmation page with a 303:
// src/routes/checkout/+page.server.ts
import { fail, redirect } from '@sveltejs/kit'
import { createOrder, chargeOrder } from '$lib/server/orders'
import type { Actions } from './$types'
export const actions: Actions = {
pay: async ({ request, locals }) => {
const form = await request.formData()
const token = String(form.get('paymentToken') ?? '')
if (!token) return fail(400, { message: 'Payment details are missing.' })
const order = await createOrder(locals.cartId, {
email: String(form.get('email')),
name: String(form.get('name')),
address: String(form.get('address')),
city: String(form.get('city')),
postal: String(form.get('postal')),
country: String(form.get('country')),
delivery: String(form.get('delivery')),
})
const paid = await chargeOrder(order, token) // your provider's server SDK
if (!paid) return fail(402, { message: 'The card was declined. Nothing was charged.' })
redirect(303, `/checkout/confirmation/${order.number}`)
},
}
On the page, the split checkout's core calls onPay once the form is valid.
Get the token from the hosted fields there and post to the action:
import { deserialize } from '$app/forms'
import { goto } from '$app/navigation'
const co = createSplitCheckout({
items: data.cart.items,
onPay: async (values) => {
const paymentToken = await hostedFields.tokenize() // the provider's client SDK
const body = new FormData()
for (const k of ['email', 'name', 'address', 'city', 'postal', 'country', 'delivery'] as const) {
body.set(k, String(values[k] ?? ''))
}
body.set('paymentToken', paymentToken)
const res = await fetch('?/pay', { method: 'POST', body, headers: { 'x-sveltekit-action': 'true' } })
const result = deserialize(await res.text())
if (result.type === 'redirect') return goto(result.location)
throw new Error(result.type === 'failure' ? String(result.data?.message) : 'Payment failed')
},
})
A throw keeps the form as it was and clears the loading state. For a redirect
provider, the action creates the order and the provider's payment session
instead, and ends with redirect(303, session.url); the provider sends the
shopper back to your confirmation route.
The stepped checkout works the same way through createSteppedCheckout({ onPlace }), which resolves with the order number, and the payment methods block
through createPaymentMethods({ onSave }). The confirmation route loads the
order and passes it in:
// src/routes/checkout/confirmation/[number]/+page.server.ts
import { error } from '@sveltejs/kit'
import { getOrder } from '$lib/server/orders'
export async function load({ params, locals }) {
const order = await getOrder(params.number, locals.user)
if (!order) error(404, 'Order not found')
return { order }
}
const done = createOrderConfirmation({
order: data.order,
writeText: (t) => navigator.clipboard.writeText(t),
})
Headless
Each block is a styled render of the core in its <script module>:
createSplitCheckout, createSteppedCheckout, createPaymentMethods and
createOrderConfirmation. The cores own the values, totals, validation and
async flows and return prop-getters such as formProps(),
summaryToggleProps(), stepperProps(), groupProps() and optionProps().
Take a core alone with npx @svgrid/ui add checkout-split --headless and draw
your own markup over it.
See also
Live examples
- Split checkout block - One-page checkout with SvTextInput, SvCountryInput, a delivery SvRadioGroup and SvMaskedInput card fields beside a sticky order summary. Pay checks every field (Luhn check, expiry not in the past), then shows SvButton loading and a toast.
- Stepped checkout block - An SvStepper with Shipping, Payment and Review: SvPhoneInput, SvCountryInput, SvRadioGroup and SvMaskedInput validated per step. Review has Edit links that jump back and return to Review; Place order shows a loading state.
- Payment methods block - Radio cards for Card, Wallet pay, Bank transfer and Invoice with arrow-key navigation; the chosen method's form appears below (SvMaskedInput card, SvPhoneInput, IBAN with a mod-97 check, or VAT number plus SvCountryInput). Continue validates only that method.
- Order confirmation block - Thank-you page with an inline-SVG check mark, a copyable order number, a delivery window counted in business days, next steps, the paid summary with an SvBadge status, and Track order / Continue shopping SvButtons.