Cart screen

ProReact Native · Expo

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.

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/cart-screen

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

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

  • Wrap the app in GestureHandlerRootView and ThemeProvider, and render <Toaster /> once for the Undo toast.

Usage

// app/cart.tsx
import { Stack, router } from "expo-router";
import { CartScreen } from "@/components/goodui-native/cart-screen/cart-screen";
import { api, useCart } from "@/lib/cart";

export default function CartRoute() {
  const cart = useCart();
  return (
    <>
      <Stack.Screen options={{ headerShown: false }} />
      <CartScreen
        items={cart.items} // [{ id, title, variant, price, quantity, image }]
        onItemsChange={cart.setItems}
        currency="EUR"
        shippingFee={4.9}
        freeShippingOver={100}
        onApplyPromo={(code) => api.checkPromo(code)} // resolve { code, label, percentOff } or reject new Error("message")
        onCheckout={(summary) => router.push("/checkout")}
        onSaveForLater={(item) => api.saveForLater(item.id)}
        onBrowse={() => router.push("/shop")}
        onBack={() => router.back()}
      />
    </>
  );
}

When to use

Use it for
  • The cart or basket of a shop, grocery or food ordering app before checkout.
  • Any list of things being bought where quantities and a running total matter.
Reach for something else
  • Picking delivery and paying: use checkout-screen after this one.
  • A wishlist without quantities or totals: use list-row rows with swipe-actions.

Examples

On its own, with sample items (try the code WELCOME10)

<CartScreen />

Always charge delivery, no undo toast

<CartScreen items={items} onItemsChange={setItems} freeShippingOver={Infinity} undo={false} />

API reference

PropTypeDefault
items
Items (controlled). Pair with `onItemsChange`.
CartItem[]—
defaultItems
Starting items when uncontrolled.
CartItem[]defaultItems
onItemsChange
Called with the new list after a quantity change or a removal.
(items: CartItem[]) => void—
currency
ISO 4217 code for every amount. Default "EUR".
string"EUR"
shippingFee
Delivery fee below `freeShippingOver`. Default 4.9.
number4.9
freeShippingOver
Subtotal (after discount) from which delivery is free. Default 100. Pass `Infinity` to always charge.
number100
onApplyPromo
Check a promo code. Resolve with the promo, or reject with an Error whose message is shown under the field.
(code: string) => Promise<CartPromo>defaultApplyPromo
defaultPromo
Promo applied when the screen opens.
CartPromo | nullnull
onCheckout
Checkout button. Receives the totals. Return a promise to show a spinner on the button.
(summary: CartSummary) => unknown—
onSaveForLater
The second swipe action, "Save for later". Hidden when omitted. The item leaves the cart.
(item: CartItem) => void—
onBrowse
Empty state action, for example navigating to the shop.
() => unknown—
onBack
Back button. Hidden when omitted.
() => void—
undo
Undo toast after a removal. Default `true`; needs the Toaster at the app root.
booleantrue
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<CartScreenLabels>—
style
Style for the outer container.
StyleProp<ViewStyle>—
testID
Identifier for end-to-end tests.
string—

Motion and haptics

  • Every total rolls: the line price when its quantity changes, the subtotal, discount, delivery and total, and the checkout bar.
  • The free delivery meter fills on the smooth spring and turns green when the order qualifies.
  • Swipe left: the actions unfold with the finger; a long swipe removes, the row slides out and collapses, and the rows below close the gap on the smooth spring.
  • A wrong code shakes the field and slides the error in; a good one swaps the field for the code chip.
  • Reduced motion: digits change in place, the meter jumps, rows close the gap at once, and the field does not shake.
  • Haptic: tick on each quantity step, delete when the trash removes the item, warning at the limits (QuantityStepper)
  • Haptic: select when a swipe crosses the full-swipe threshold, delete when the remove action runs (SwipeActions); notify for the toast, undo on its Undo
  • Haptic: tap on Apply and Check out, success or error when Apply settles (Button); error when the field shows an error (TextField)
  • Haptic: tap on removing the promo chip
  • Quantities, swipes and rolls animate on the UI thread; React renders on item, promo and keyboard changes.
  • Rows render in a ScrollView: built for the tens of items a cart holds.

Accessibility

  • 'Cart' is the header with the item count next to it.
  • Each row's stepper is adjustable and named 'Quantity, <title>'; at one, minus becomes a labelled remove button.
  • Swipe actions are also screen reader actions on the row (Remove, Save); a removal is announced, and Undo is in a toast.
  • The promo field announces its error under the field; an applied code is announced and shown as a chip with a remove button.
  • Totals are read as plain numbers; the free delivery line is visible text ('Add €12.40 more for free delivery').

Notes for AI

  • Controlled: pass items and onItemsChange (called after every quantity change and removal). Uncontrolled: pass defaultItems.
  • onApplyPromo resolves with { code, label, percentOff } or { code, label, amountOff }, or rejects with an Error whose message is shown under the field. The default accepts WELCOME10 only, for previews.
  • Delivery is free when the subtotal after discount reaches freeShippingOver; pass Infinity to always charge shippingFee.
  • The Undo toast needs <Toaster /> once at the app root; pass undo={false} if you have no Toaster.
  • Render it full-bleed with the navigator header hidden (headerShown: false). It handles the safe areas and lifts the promo field above the keyboard on native, hiding the checkout bar while typing.