Checkout
The checkout: one section open at a time, the others collapsed to a summary or waiting their turn, and the summary aside. The site supplies the section set and composes each section's body. Render it on the client once the cart has loaded.
import { Checkout } from "@evolve-storefront/feature-cart/checkout";Renders inside CartProvider.
Preview
Usage
A guest from the top: each section opens after the one above is confirmed.
const sections = [ { id: "details", title: t("details"), isComplete: (cart) => Boolean(cart.customerEmail) }, { id: "delivery", title: t("delivery"), isComplete: (cart) => Boolean(cart.selectedShippingMethod), fingerprint: (cart) => ({ country: cart.shippingAddress?.country ?? "" }) }, { id: "payment", title: t("payment"), isComplete: (cart) => Boolean(cart.selectedPaymentMethod) },] as const;
<Checkout cart={cart} sections={sections}> <CheckoutMain> <CheckoutSection sectionId="details" hint={t("details-hint")} summary={cart.customerEmail}> <DetailsFields /> <CheckoutSectionContinue onContinue={saveDetails} /> </CheckoutSection> … <CheckoutSection sectionId="payment" notice={paymentFailed ? t("payment-failed") : null}> <PaymentMethods /> <CheckoutPlaceOrder amount={formatMoney(cart.total.gross, locale)} onPlaceOrder={placeOrder} /> </CheckoutSection> </CheckoutMain> <CheckoutAside><CartSummary cart={cart} /></CheckoutAside></Checkout>;Props
| Prop | Type | Description |
|---|---|---|
cart | CheckoutCartBase & AnalyticsSourceCart | The cart being checked out. Sections read whether they are complete from it. |
sections | readonly CheckoutSectionDefinition<CheckoutCartBase>[] | The site's section set, in page order. The last one holds |
children | ReactNode |
|
confirmations? | CheckoutConfirmationStore | Where confirmed sections survive a reload. |
layout? | CheckoutLayout |
|
Any other props go to the root element, like on a plain HTML element.
Change the layout
Pass children to Checkout to arrange its parts yourself. Every part is exported from @evolve-storefront/feature-cart/checkout.
const sections = [ { id: "details", title: t("details"), isComplete: (cart) => Boolean(cart.customerEmail) }, { id: "delivery", title: t("delivery"), isComplete: (cart) => Boolean(cart.selectedShippingMethod), fingerprint: (cart) => ({ country: cart.shippingAddress?.country ?? "" }) }, { id: "payment", title: t("payment"), isComplete: (cart) => Boolean(cart.selectedPaymentMethod) },] as const;
<Checkout cart={cart} sections={sections}> <CheckoutMain> <CheckoutSection sectionId="details" hint={t("details-hint")} summary={cart.customerEmail}> <DetailsFields /> <CheckoutSectionContinue onContinue={saveDetails} /> </CheckoutSection> … <CheckoutSection sectionId="payment" notice={paymentFailed ? t("payment-failed") : null}> <PaymentMethods /> <CheckoutPlaceOrder amount={formatMoney(cart.total.gross, locale)} onPlaceOrder={placeOrder} /> </CheckoutSection> </CheckoutMain> <CheckoutAside><CartSummary cart={cart} /></CheckoutAside></Checkout>;CheckoutAsideThe summary beside the sections from lg up, sticky so the total stays in view.
No props of its own.
CheckoutBackToCartLabelWhat goes inside the site's back link: an arrow, and "Back to cart", which only the arrow stands in for on narrow screens.
No props of its own.
CheckoutBusinessPurchase"I'm ordering for a business": a box that, ticked, asks for the company name and an optional VAT number.
| Prop | Type | Description |
|---|---|---|
value | CheckoutBusinessPurchaseValue | |
onValueChange | (value: CheckoutBusinessPurchaseValue) => void | |
country? | string | The billing country, for a VAT number typed without its prefix. |
vatCheck? | boolean | Check the VAT number live against the register. Needs the framework's VIES module. |
companyError? | string | Shown under the company name after a submit without one. |
Any other props go to the root element, like on a plain HTML element.
CheckoutCarrierWho delivers: the carrier's logo, with its name as alt text, or its name alone when the key has no logo or the image fails to load.
| Prop | Type | Description |
|---|---|---|
carrier | Carrier | |
logos? | Readonly<Record<string, string>> |
Any other props go to the root element, like on a plain HTML element.
CheckoutDeliveryEstimateWhen a method arrives: "today", "tomorrow", a date or a range.
| Prop | Type | Description |
|---|---|---|
estimate | MethodEstimate | null | From |
timeZone? | string | The zone "today" and the order-before time are in, the store's. The shopper's own by default. |
Any other props go to the root element, like on a plain HTML element.
CheckoutDeliverySectionDelivery: the address and how it is delivered, with the cart's method, or the first on offer, preselected so confirming costs one click.
| Prop | Type | Description |
|---|---|---|
cart | Cart | |
hint? | ReactNode | What an upcoming section will ask, beside its title. |
notice? | ReactNode | One line above the open body: a failed payment, or why the section reopened. |
headingLevel? | HeadingLevel | |
sectionId? | string | Defaults to |
detailsSectionId? | string | The section whose confirmed name and phone go on the address. |
before? | ReactNode | Above the address: a home-or-pickup choice. |
deliveryOptions? | ReactNode | Under the methods: anything else the delivery needs. |
slots? | boolean | Ask for a delivery date and time slot instead of a method; the slot sets the method. Needs the framework's delivery-options module. |
requestedDate? | boolean | Ask whether backordered lines ship complete or split, and for an optional
requested delivery date. Needs delivery preferences in the framework's
delivery-options module; not with |
timeZone? | string | The zone slot windows are shown in, the store's. The shopper's own by default. |
pickup? | boolean | Offer collecting from a pickup point beside home delivery. Needs the framework's delivery-options module with a pickup provider. |
defaultCountry? | string | The country a postal-code search for pickup points is in, without an address. |
warehouse? | CheckoutWarehouse | Offer collecting at the seller's warehouse: no address is asked, and its shipping method is set. Its key is never offered as a home delivery method. |
isSignedIn? | boolean | Whether the shopper is signed in; only then can an address be saved. |
saveToAddressBook? | boolean | Offer "Save in my address book" for a newly typed address, ticked by default. |
onSaveAddress? | CheckoutSaveAddress | Saves the address once the cart has it, when the shopper left the box ticked. |
savedAddresses? | readonly CartDeliveryAddress[] | The signed-in shopper's address book, offered as cards above "Another address". The cart's address is preselected when it is one of them; without one, the default. A typed address already in it is not saved again. |
renderAddressPicker? | CheckoutAddressPickerRenderer | Pick from the company's addresses instead of typing one, such as
feature-organization's |
allowOneOffAddress? | boolean | With a picker: whether the buyer may type a one-off address instead. |
estimates? | boolean | Show each method's carrier and when it arrives. Needs the framework's
delivery-options module; not with |
carrierLogos? | Readonly<Record<string, string>> | Logo URLs by carrier key, for the methods with |
companyProfileHref? | string | The company profile, linked from the VAT outcome when the company lacks a VAT number. |
Any other props go to the root element, like on a plain HTML element.
CheckoutDeliverySlotsWhen to deliver: a chip per date with its lowest price, then the slots on the chosen date with what each costs extra and its carrier.
| Prop | Type | Description |
|---|---|---|
dates | readonly DeliveryDate[] | |
value | DeliverySlot | null | The chosen slot. |
onValueChange | (slot: DeliverySlot) => void | |
timeZone? | string | The zone a window is shown in. The shopper's own by default. |
carrierLogos? | Readonly<Record<string, string>> | Logo URLs by carrier key. A carrier without one is named under the window. |
Any other props go to the root element, like on a plain HTML element.
CheckoutDetailsSectionDetails: the email for the confirmation, and the recipient's name and phone.
| Prop | Type | Description |
|---|---|---|
cart | Cart | |
hint? | ReactNode | What an upcoming section will ask, beside its title. |
notice? | ReactNode | One line above the open body: a failed payment, or why the section reopened. |
headingLevel? | HeadingLevel | |
sectionId? | string | Defaults to |
account? | ReactNode | Under the email: the site's account detection and sign-in. |
onEmailSettled? | ((email: string) => void) | The email field lost focus holding a valid address, for account detection. |
Any other props go to the root element, like on a plain HTML element.
CheckoutFinishSectionFinishing a business checkout: the purchase reference, then ordering or another ending such as a quote, preselected from the cart button and switchable here.
| Prop | Type | Description |
|---|---|---|
cart | Cart | |
onPlaceOrder | (cart: Cart) => unknown | Places the order once the reference, billing and method are on the cart. |
hint? | ReactNode | What an upcoming section will ask, beside its title. |
notice? | ReactNode | One line above the open body: a failed payment, or why the section reopened. |
headingLevel? | HeadingLevel | |
sectionId? | string | Defaults to |
requirePurchaseReference? | boolean | The company requires a purchase reference, so the field says so and checks it. |
fields? | ReactNode | Under the reference: the site's own fields, such as a cost center. |
endings? | readonly CheckoutEnding[] | The ways to finish beside ordering, such as |
defaultEnding? | string | The ending chosen on arrival: the cart button the buyer clicked. Ordering by default. |
placeOrderError? | unknown | |
placeOrderBlockedReason? | ReactNode | Why the order cannot be placed, such as |
amount? | string | What the order button shows. The cart's gross total by default. |
billingAddress? | CartShippingAddressInput | The invoice address, usually the company's. The cart's own billing or shipping address without it. |
renderMethodIcon? | ((method: PaymentMethod) => ReactNode) | |
terms? | ReactNode | Under the order button: the business terms. |
device? | ClientDevice |
Any other props go to the root element, like on a plain HTML element.
CheckoutGiftCardsGift cards in Payment: number and PIN, a balance check, then "Use".
| Prop | Type | Description |
|---|---|---|
cart | Cart | |
pending | CheckoutPendingGiftCard | null | The card that pays the rest once the order is placed. |
onPendingChange | (pending: CheckoutPendingGiftCard | null) => void | |
error? | unknown | Why the pending card failed when the order was placed. |
Any other props go to the root element, like on a plain HTML element.
CheckoutHeaderThe checkout's own header, in three regions: back to the cart, logo, and an end slot.
| Prop | Type | Description |
|---|---|---|
backToCart? | ReactNode | The way back, the site's link: |
logo? | ReactNode | The centre region, usually the site's logo. |
children? | ReactNode | The end region: trust cues such as "Veilig afrekenen", or a company switcher. |
Any other props go to the root element, like on a plain HTML element.
CheckoutMainThe column of sections, and whatever a site puts above them: an identity block, express checkout.
No props of its own.
CheckoutPaymentSectionPayment: who gets the invoice and how they pay, with the final button in the flow.
| Prop | Type | Description |
|---|---|---|
cart | Cart | |
onPlaceOrder | (cart: Cart) => unknown | Places the order once billing and the method are on the cart: place, pay and
route. Return |
hint? | ReactNode | What an upcoming section will ask, beside its title. |
notice? | ReactNode | One line above the open body: a failed payment, or why the section reopened. |
headingLevel? | HeadingLevel | |
sectionId? | string | Defaults to |
detailsSectionId? | string | The section whose confirmed name and phone go on the billing address. |
placeOrderError? | unknown | The place-order mutation's error, shown under the button. |
amount? | string | What the button shows. The cart's gross total by default. |
business? | ReactNode | Under the billing address: an "I'm ordering for a business" block. |
giftCards? | boolean | Offer gift cards above the methods. Needs the framework's gift card module, plus
|
onPayRest? | ((cart: Cart, paymentMethod: PaymentMethodInput) => unknown) | Called instead of |
onPaidInFull? | ((order: { id: string; orderNumber: string; }) => unknown) | Called instead of |
renderMethodIcon? | ((method: PaymentMethod) => ReactNode) | A method's logo, by method. |
terms? | ReactNode | Under the button: the terms. |
device? | ClientDevice | |
deliverySectionId? | string | The section whose confirmed choice says whether the parcel goes to a pickup point. |
pickup? | boolean | The checkout offers pickup points, so a reload can tell from the cart. |
warehouseMethodKey? | string | Delivery's |
businessPurchase? | boolean | Offer "I'm ordering for a business", with the company name on the invoice. |
vatCheck? | boolean | Check the business VAT number live. Needs the framework's VIES module. |
isSignedIn? | boolean | Whether the shopper is signed in; only then can an address be saved. |
saveToAddressBook? | boolean | Offer "Save in my address book" for a newly typed billing address, ticked by default. |
onSaveAddress? | CheckoutSaveAddress | Saves the billing address once the cart has it, when the shopper left the box ticked. |
savedAddresses? | readonly CartDeliveryAddress[] | The signed-in shopper's address book, offered as cards when billing is not the delivery address. A typed billing address already in it is not saved again. |
Any other props go to the root element, like on a plain HTML element.
CheckoutPickupPointsWhere to collect: a postal-code search, a chip per carrier with how many points it has, and the points nearest first, each with its distance, today's hours, earliest pickup and price.
| Prop | Type | Description |
|---|---|---|
points | readonly PickupPoint[] | |
value | PickupPoint | null | |
onValueChange | (point: PickupPoint) => void | |
query | string | The postal code searched near. |
onSearch | (postalCode: string) => void | |
isPending? | boolean | |
timeZone? | string | The zone opening hours and pickup times are shown in, the store's. |
carrierLogos? | Readonly<Record<string, string>> | Logo URLs by carrier key, for the carrier chips. A carrier without one is named. |
Any other props go to the root element, like on a plain HTML element.
CheckoutPlaceOrderThe final button, in the last section's flow.
| Prop | Type | Description |
|---|---|---|
isPending? | boolean | The action is running. The button keeps its label, so its width and
accessible name hold, shows a spinner and is |
tone? | ButtonTone | |
isIconOnly? | boolean | The button has no visible label, so it is square. Structural rather than a size: it says the button has nothing to be wide for, and says nothing about how big — the brand's control tokens still decide that. MoreNot inferred from the children. A kebab menu is as often a glyph or an SVG
as an |
data-component? | undefined | A button's identity is not a caller's to rename — see |
amount? | string | What the shopper pays, formatted. The button shows it. |
onPlaceOrder? | (() => unknown) | Saves the last section and places the order. Return |
type? | "button" | "submit" |
|
pendingLabel? | ReactNode | What a screen reader announces while pending, for a button that does something other than order. |
terms? | ReactNode | Under the button: the terms, a cooling-off period. |
error? | unknown | The place-order mutation's error, shown under the button in the shopper's words. |
missing? | readonly { id: string; title: string; onSelect: () => void; }[] | What the current section still lacks, named beside the missing sections, such as
a payment method that is no longer offered. |
blockedReason? | ReactNode | Why the order cannot be placed whatever the shopper fills in, such as
|
Any other props go to the root element, like on a plain HTML element.
CheckoutSectionOne section of the checkout.
| Prop | Type | Description |
|---|---|---|
sectionId | string | The id of one of the definitions the |
hint? | ReactNode | What an upcoming section will ask, beside its title. |
summary? | ReactNode | The confirmed value, shown while the section is done. Never the draft. |
notice? | ReactNode | One line above the open body: a failed payment, or why the section reopened. |
headingLevel? | HeadingLevel | |
children? | ReactNode | The open body: the section's fields and its one primary button. |
Any other props go to the root element, like on a plain HTML element.
CheckoutSectionContinueThe section's one primary button, at the end of its body.
| Prop | Type | Description |
|---|---|---|
isPending? | boolean | The action is running. The button keeps its label, so its width and
accessible name hold, shows a spinner and is |
tone? | ButtonTone | |
isIconOnly? | boolean | The button has no visible label, so it is square. Structural rather than a size: it says the button has nothing to be wide for, and says nothing about how big — the brand's control tokens still decide that. MoreNot inferred from the children. A kebab menu is as often a glyph or an SVG
as an |
data-component? | undefined | A button's identity is not a caller's to rename — see |
onContinue? | (() => unknown) | Validates and saves the section. Return |
type? | "button" | "submit" |
|
Any other props go to the root element, like on a plain HTML element.
CheckoutStepperThe steps of a multi-step checkout, numbered, with the open one marked current and each confirmed one a button back to it.
No props of its own.
CheckoutSummaryLinesThe cart's lines, compact and read-only: image, name, quantity and line total in the summary's VAT basis.
| Prop | Type | Description |
|---|---|---|
lines | readonly CartLineItem[] | The cart's lines, |
vatDisplay? | VatDisplay | The basis of the line totals; pass the summary's. Defaults to gross. |
Any other props go to the root element, like on a plain HTML element.
CheckoutSummaryToggleThe order summary on a phone: a bar at the top of the checkout, "Show summary · €x", that opens the summary in place.
| Prop | Type | Description |
|---|---|---|
total | string | What the shopper pays, formatted, shown on the bar while the summary is closed. |
children | ReactNode | The summary itself, usually |
defaultOpen? | boolean |
Any other props go to the root element, like on a plain HTML element.
CheckoutTermsNotice"By placing your order, you agree to our terms and conditions", linking the terms page.
| Prop | Type | Description |
|---|---|---|
termsPath | string | null | undefined | The terms page's path, such as |
renderLink? | ((props: { href: string; children: ReactNode; }) => ReactNode) | The link with the app's router. A plain anchor by default. |
Any other props go to the root element, like on a plain HTML element.
CheckoutVatOutcomeWhich VAT rule the cart follows and why, such as "VAT reverse-charged · Delivered to another EU country for a business with a valid VAT number." An export adds its delivery terms and the company's EORI number, where set.
| Prop | Type | Description |
|---|---|---|
outcome | VatOutcome | null | undefined | The cart's |
companyProfileHref? | string | The company profile, where an admin adds a VAT number; links the hint that asks for one. |
Any other props go to the root element, like on a plain HTML element.
ExpressCheckoutWallet buttons, above the first checkout field or in the cart: each wallet the cart offers, through renderButton, and nothing when it offers none.
| Prop | Type | Description |
|---|---|---|
cart | Cart | |
renderButton | (method: PaymentMethod, handlers: ExpressCheckoutHandlers) => ReactNode | One wallet's button, from the payment provider's integration. |
onPlaceOrder | (cart: Cart) => unknown | Places and pays the saved cart. Return |
device? | ClientDevice | |
isShippingMethodOffered? | ((method: ShippingMethod) => boolean) | Leave out a method that needs the full checkout, such as a slot or pickup method. |
maxShippingOptions? | number | How many shipping options the sheet shows. Defaults to 3. |
returnUrls? | CheckoutReturnUrls | Where the provider sends the shopper back; needed by a wallet that pays before the order. |
onPaid? | ((outcome: { kind: "paid"; order: { id: string; orderNumber: string; } | null; pending: boolean; }) => unknown) | A wallet that pays before the order finished: route to the order, or to a confirming page. |
Any other props go to the root element, like on a plain HTML element.
Loading and empty states
Render these while the data loads or when there is nothing to show, so the page does not jump when it arrives.
| Loading | CheckoutSkeleton | The checkout's shape while the cart loads: the first section open, the rest collapsed, and the summary. |
Examples
LoadingSkeleton and real checkout adjacent, so a placeholder that drifts from the sections and summary it stands in for shows here.
// One placeholder per section, so the page does not shift when the cart lands.if (cart.isPending) return <CheckoutSkeleton sections={sections.length} />;
return <Checkout cart={cart.data} sections={sections}>…</Checkout>;Draft SurvivesA draft outlives its section closing, and the summary keeps the confirmed value.
<Checkout cart={cart} sections={sections}>…</Checkout>;Return From Failed PaymentBack from a failed payment: the sections above are confirmed from storage, and Payment opens with the site's notice.
<Checkout cart={cart} sections={sections}>…</Checkout>;Delivery InvalidatedA stored delivery confirmation for another country is dropped, and says why.
<Checkout cart={cart} sections={sections}>…</Checkout>;Multi StepThe same sections one at a time: a stepper above, the confirmed ones as a recap with "Change", and nothing of what is still to come.
<Checkout cart={cart} sections={sections} layout="multi"><CheckoutMain><CheckoutStepper />…</CheckoutMain></Checkout>;Below MinimumBelow the business unit's minimum order value: the button is off whatever is filled in, and says why.
const minimum = useCartMinimumOrder();
<CheckoutPlaceOrder amount={amount} onPlaceOrder={placeOrder} blockedReason={minimum.isBelowMinimum ? <CartMinimumOrderReason minimumOrder={minimum.data} /> : undefined}/>;Refused Below MinimumThe server refused the order as below the minimum: the shopper reads why, and what to do.
<CheckoutPlaceOrder amount={amount} onPlaceOrder={placeOrder} error={place.error} />;
