Checkout screen

ProReact Native · Expo

The last step before paying: delivery address and payment method pickers, delivery options, the order summary with totals that roll when the delivery changes, and slide to pay in thumb reach. Paying covers the screen with a success check and confetti, then hands over through onDone.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add @goodui-native-pro/checkout-screen

Then run npx expo install --fix so react-native-svg match your Expo SDK. Files land in components/goodui-native/checkout-screen.

Pro screens install from the license-gated @goodui-native-pro registry. Setup: Pro access.

  • Wrap the app in GestureHandlerRootView and ThemeProvider (sheets and the success check render in its overlay layer).

Usage

// app/checkout.tsx
import { Stack, router } from "expo-router";
import { CheckoutScreen } from "@/components/goodui-native/checkout-screen/checkout-screen";
import { api, useCart, useProfile } from "@/lib/shop";

export default function CheckoutRoute() {
  const cart = useCart();
  const { addresses, cards } = useProfile();
  return (
    <>
      <Stack.Screen options={{ headerShown: false }} />
      <CheckoutScreen
        items={cart.items}
        discount={cart.discount}
        currency="EUR"
        addresses={addresses} // [{ id, label: "Home", name, line }]
        paymentMethods={cards.map((c) => ({ id: c.id, label: `${c.brand} •••• ${c.last4}`, description: `Expires ${c.exp}` }))}
        deliveryOptions={[
          { id: "standard", label: "Standard", description: "Thu 6 to Mon 10 Nov", price: 0 },
          { id: "express", label: "Express", description: "Arrives tomorrow", price: 9.9 },
        ]}
        onPay={(order) => api.placeOrder(order)} // resolve { orderNumber, arrives } or reject new Error("Card declined")
        onDone={(receipt) => router.replace(`/orders/${receipt?.orderNumber}`)}
        onBack={() => router.back()}
      />
    </>
  );
}

When to use

Use it for
  • Confirming a shop, grocery or food order: where it goes, how it is paid, how fast it arrives.
  • Any purchase where a stray tap must not pay: slide to pay asks for a deliberate gesture.
Reach for something else
  • Editing quantities and promo codes: do that in cart-screen first.
  • Subscriptions: use paywall-screen.
  • Collecting a new card number: open your payment provider's sheet from the payment step; this screen picks among saved methods.

Examples

On its own, with sample addresses, cards and items

<CheckoutScreen onDone={() => router.replace("/orders")} />

A payment that can be declined

<CheckoutScreen onPay={async (order) => { const r = await api.pay(order); if (!r.ok) throw new Error("Your bank declined the payment."); return { orderNumber: r.id }; }} />

API reference

PropTypeDefault
addressesCheckoutAddress[]defaultAddresses
paymentMethodsCheckoutPaymentMethod[]defaultPayments
deliveryOptionsCheckoutDeliveryOption[]—
itemsCheckoutItem[]defaultItems
discount
Amount taken off the subtotal, for example from a promo code.
number0
currency
ISO 4217 code for every amount. Default "EUR".
string"EUR"
defaultAddressId
Starting selections. Default the first of each.
string—
defaultPaymentMethodIdstring—
defaultDeliveryOptionIdstring—
onPay
Runs when the slider reaches the end. Return a promise that resolves with the order number; the slider shows the pending state, then the success check covers the screen. A rejection shows its message in a banner.
(order: CheckoutOrder) => Promise<CheckoutReceipt | void>() => new Promise((resolve) => setTimeout(() => resolve({ orderNumber: "PN-48213" }), 1200))
onDone
After the success check, on its timer or a tap. Navigate to the order here.
(receipt: CheckoutReceipt | null) => void—
onBack
Back button. Hidden when omitted.
() => void—
locale
BCP 47 locale for amounts. Defaults to the device locale.
string—
labels
Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide.
Partial<CheckoutScreenLabels>—
style
Style for the outer container.
StyleProp<ViewStyle>—
testID
Identifier for end-to-end tests.
string—

Motion and haptics

  • Changing the delivery option rolls the delivery fee and the total, and the slider's 'Slide to pay' amount updates.
  • Slide to pay follows the finger 1:1, ticks at the point of no return, gathers into a spinner while onPay runs, then the success check springs in over the screen, draws its check and bursts confetti.
  • A rejection opens the slider into 'Declined' with a shake, springs the thumb back and slides the banner down.
  • Reduced motion: digits change in place, the slider's releases jump and its states crossfade, the check appears complete without ring or confetti physics, the banner fades.
  • Haptic: select when the slider crosses the point of no return; confirm or error when onPay settles (SwipeToConfirm)
  • Haptic: celebrate as the check lands with its confetti (SuccessCheck)
  • Haptic: select when a delivery row or a Select option is picked (ListRow, Select)
  • Haptic: error when the decline banner appears (Banner)
  • The slider and success check animate on the UI thread; Select sheets mount only while open; React renders on selection changes and payment results.

Accessibility

  • 'Checkout' is the header; Back is a labelled icon button.
  • Address and payment are Select fields named by their labels ('Deliver to', 'Pay with') that open a sheet list with checks.
  • Delivery options are checkable rows (radio behaviour); picking one announces the option and the new total.
  • Slide to pay is one button: screen readers activate it with a double tap, the web with Enter or Space; the result is announced.
  • A declined payment shows a danger banner with the reason, announced when it appears. The success check is announced and closes on a tap or Escape.

Notes for AI

  • onPay receives { addressId, paymentMethodId, deliveryOptionId, total } and must return a promise: resolve with { orderNumber, arrives? } (shown under the check), or reject with an Error whose message is shown in the banner.
  • onDone runs after the success check closes (about 2.2 s, or a tap); navigate to the order there. The slider stays confirmed (resetAfter={false}).
  • Without deliveryOptions the screen offers Standard (free), Express (9.90) and Pick up in store with dates from the device clock; pass your own options with a price per option (0 shows Free).
  • The slider is disabled until an address, a payment method and a delivery option are selected and there is at least one item.
  • Render it full-bleed with the navigator header hidden (headerShown: false); it handles the safe areas itself.
Cart screen

A shopping cart: line items with a quantity stepper whose minus turns into remove, swipe left to remove or save for later, a promo code field, a free delivery meter and totals that roll with every change above a checkout bar. Removing an item offers Undo; an empty cart shows its own empty state.

Order tracking screen

Live order status: the arrival estimate with minutes that roll as they count down, a timeline whose line fills into each step as the order moves on (finished steps draw a check, the current one breathes), the courier with call and message buttons, and the items. Delivery lands a success check and asks for a rating.

Swipe to confirm

Slide to confirm: the thumb follows the finger 1:1, the track fills behind it and the label fades, a tick marks the point of no return, and at the end the track gathers into a spinner and opens into the result.

Success check

A success or error moment: the circle springs in, the check draws itself while a ring pulses out and the title rises in; the error variant draws a cross and shakes once. Inline or fullscreen, optionally with confetti.

Select

A field that opens a bottom sheet list with search, sections and checkmarks that draw themselves in. Single mode closes on a pick; multiple mode shows the picks as chips in the field that grow in and shrink away live behind the sheet.

List row

Settings and navigation cells with a token-colored icon tile, title, subtitle and a trailing value, switch, badge, check or chevron, grouped in inset sections with headers and footers. On touch down the highlight floods in, the tile sinks and the chevron leans forward; on release the highlight lingers and fades.