v0.1 public beta early access on GitHub
Home / Screens / ecommerce-cart
Screen template

Cart Screen

Line items with thumbnails, quantity steppers and line totals, free-shipping progress with unlocked state, applied-promo row with live discount, and a shadowed sticky checkout bar

pro Expo Go ✓ iosandroid gestures: tap · scroll haptics: light · selection · warning · heavy
$ npx lumioui add ecommerce-cart
import { CartScreen } from "@/screens/ecommerce/cart";
Native packages installed6
@lumioui/ui@latest@lumioui/core@latestreact-native-safe-area-context@*react-native-reanimated@*react-native-svg@*nativewind@*
ecommerce/cart.tsxsource · yours to edit
import * as React from "react";
import { useState } from "react";
import { View, FlatList } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Button, Card, EmptyState, Glass, Icon, Image, Input, Progress, Separator, Stepper, Text } from "@lumioui/ui";
import { useHaptic } from "@lumioui/core";
import { catalogImg, LumioMark, Mount, Press, ROW_STAGGER, ROW_STAGGER_CAP } from "../shared";
import { StickyBottomBar } from "../shared";

export interface CartItem {
  id: string;
  name: string;
  brand?: string;
  variantLabel?: string;
  price: number;
  quantity: number;
  imageUrl?: string;
}

export interface CartScreenProps {
  items?: CartItem[];
  currency?: string;
  shippingCost?: number;
  /** Free shipping above this subtotal; set shippingCost for the fallback. */
  freeShippingThreshold?: number;
  onUpdateQty?: (item: CartItem, quantity: number) => void;
  onRemove?: (item: CartItem) => void;
  onApplyPromo?: (code: string) => void;
  onCheckout?: () => void;
  onBrowse?: () => void;
}

const img = (p: Parameters<typeof catalogImg>[0]) => catalogImg(p, 200, 250);

const DEFAULT_ITEMS: CartItem[] = [
  { id: "1", name: "Structured Wool Overcoat", brand: "Nord Standard", variantLabel: "Camel · M", price: 248, quantity: 1, imageUrl: img("overcoat") },
  { id: "3", name: "Leather Lace-Up Boots", brand: "Marchetti & Sons", variantLabel: "Black · 42", price: 295, quantity: 1, imageUrl: img("boots") },
  { id: "7", name: "Heritage Graphic Tee", brand: "Atelier M", variantLabel: "Indigo", price: 85, quantity: 2, imageUrl: img("graphicTee") },
];

/** Prices always render with two decimals ($248.00). */
const fmt = (n: number, currency = "$") =>
  `${currency}${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;

function SummaryRow({
  label,
  value,
  bold,
  accent,
}: {
  label: string;
  value: string;
  bold?: boolean;
  /** Success-green value — used for promo discounts. */
  accent?: boolean;
}) {
  return (
    <View className="flex-row items-center justify-between py-1">
      <Text variant={bold ? "large" : "muted"}>{label}</Text>
      <Text
        variant={bold ? "large" : "small"}
        className={accent ? "font-semibold tabular-nums text-success" : "tabular-nums"}
      >
        {value}
      </Text>
    </View>
  );
}

/** Bag — line items with thumbnails, qty steppers and remove affordances,
 *  promo field, order summary, and a sticky checkout bar. */
export function CartScreen({
  items = DEFAULT_ITEMS,
  currency = "$",
  shippingCost = 6.99,
  freeShippingThreshold = 150,
  onUpdateQty,
  onRemove,
  onApplyPromo,
  onCheckout,
  onBrowse,
}: CartScreenProps) {
  const haptics = useHaptic();
  const [promo, setPromo] = useState("");
  /** Code currently applied — swaps the promo field for a success row. */
  const [appliedPromo, setAppliedPromo] = useState<string | null>(null);

  const subtotal = items.reduce((sum, i) => sum + i.price * i.quantity, 0);
  const shipping = items.length === 0 || subtotal >= freeShippingThreshold ? 0 : shippingCost;
  // Template discount — a real host wires this to onApplyPromo's response.
  const discount = appliedPromo ? Math.round(subtotal * 0.1 * 100) / 100 : 0;
  const total = subtotal - discount + shipping;
  const remaining = Math.max(0, freeShippingThreshold - subtotal);
  const itemCount = items.reduce((sum, i) => sum + i.quantity, 0);

  return (
    <SafeAreaView className="flex-1 bg-[#F4F5F9] dark:bg-[#111114]" edges={["top"]}>
      <View className="px-6 pb-2 pt-4">
        <View className="flex-row items-center gap-2 pb-1.5">
          <LumioMark size={18} />
          <Text variant="muted" className="text-xs font-semibold uppercase tracking-widest">
            Lumio Market
          </Text>
        </View>
        <Text variant="h2">
          Bag{itemCount > 0 ? ` (${itemCount})` : ""}
        </Text>
      </View>

      {items.length === 0 ? (
        <View className="flex-1">
          <EmptyState
            title="Your bag is empty"
            description="Items you add will appear here."
            action={
              onBrowse ? (
                <Button variant="secondary" onPress={onBrowse}>
                  Continue shopping
                </Button>
              ) : undefined
            }
            className="flex-1"
          />
        </View>
      ) : (
        <>
          <FlatList
            data={items}
            keyExtractor={(item) => item.id}
            className="flex-1"
            contentContainerClassName="px-6 pb-56"
            contentInsetAdjustmentBehavior="automatic"
            showsVerticalScrollIndicator={false}
            ListHeaderComponent={
              <View
                className="mb-4 gap-2 rounded-2xl bg-[#EDF8C4] p-4 dark:bg-[#1E2410]"
                accessibilityRole="text"
                accessibilityLabel={
                  remaining > 0
                    ? `${fmt(remaining, currency)} away from free shipping`
                    : "Free shipping unlocked"
                }
              >
                {remaining > 0 ? (
                  <>
                    <Text variant="small" className="font-medium">
                      You're {fmt(remaining, currency)} away from{" "}
                      <Text variant="small" className="font-semibold text-[#65A30D]">
                        free shipping
                      </Text>
                    </Text>
                    <Progress value={(subtotal / freeShippingThreshold) * 100} className="h-1.5" />
                  </>
                ) : (
                  <View className="flex-row items-center gap-1.5">
                    <View className="h-5 w-5 items-center justify-center rounded-full bg-success">
                      <Icon name="check" size="sm" color="successForeground" />
                    </View>
                    <Text variant="small" className="font-medium text-success">
                      Free shipping unlocked
                    </Text>
                  </View>
                )}
              </View>
            }
            ItemSeparatorComponent={() => <View className="h-3" />}
            renderItem={({ item, index }) => (
              <Mount delay={Math.min(index * ROW_STAGGER, ROW_STAGGER_CAP)}>
                <View className="flex-row gap-3 overflow-hidden rounded-3xl bg-white p-3 dark:bg-[#1A1A20]">
                <Image
                  source={item.imageUrl ? { uri: item.imageUrl } : undefined}
                  rounded="xl"
                  aspectRatio={4 / 5}
                  className="w-24"
                  accessibilityLabel={`Photo of ${item.name}`}
                />
                <View className="flex-1">
                  <View className="flex-row items-start justify-between">
                    <View className="flex-1 gap-0.5 pr-2">
                      <Text variant="small" numberOfLines={1}>
                        {item.name}
                      </Text>
                      {item.brand || item.variantLabel ? (
                        <Text variant="muted" className="text-xs" numberOfLines={1}>
                          {[item.brand, item.variantLabel].filter(Boolean).join(" · ")}
                        </Text>
                      ) : null}
                    </View>
                    <Press
                      onPress={() => {
                        haptics.warning();
                        onRemove?.(item);
                      }}
                      accessibilityRole="button"
                      accessibilityLabel={`Remove ${item.name} from bag`}
                      hitSlop={12}
                      className="h-11 w-11 items-center justify-center"
                    >
                      <Icon name="trash" size="sm" color="mutedForeground" />
                    </Press>
                  </View>
                  <View className="flex-row items-center justify-between pt-2">
                    <Stepper
                      value={item.quantity}
                      min={0}
                      onValueChange={(qty) =>
                        qty === 0 ? onRemove?.(item) : onUpdateQty?.(item, qty)
                      }
                      accessibilityLabel={`Quantity for ${item.name}`}
                    />
                    <View className="items-end">
                      <Text
                        variant="small"
                        className="font-semibold tabular-nums"
                        accessibilityLabel={`${item.name} line total ${fmt(item.price * item.quantity, currency)}`}
                      >
                        {fmt(item.price * item.quantity, currency)}
                      </Text>
                      {item.quantity > 1 ? (
                        <Text variant="muted" className="text-xs tabular-nums">
                          {fmt(item.price, currency)} each
                        </Text>
                      ) : null}
                    </View>
                  </View>
                </View>
                </View>
              </Mount>
            )}
          />

          <StickyBottomBar className="gap-4">
            {appliedPromo ? (
              <View className="flex-row items-center justify-between rounded-2xl bg-success/10 py-1 pl-4">
                <View className="flex-1 flex-row items-center gap-2">
                  <Icon name="check-circle" size="sm" color="success" />
                  <Text variant="small">
                    Promo <Text variant="small" className="font-semibold">{appliedPromo}</Text> applied — 10% off
                  </Text>
                </View>
                <Press
                  onPress={() => {
                    haptics.light();
                    setAppliedPromo(null);
                    setPromo("");
                  }}
                  accessibilityRole="button"
                  accessibilityLabel={`Remove promo code ${appliedPromo}`}
                  hitSlop={12}
                  className="h-11 w-11 items-center justify-center"
                >
                  <Icon name="x" size="sm" color="mutedForeground" />
                </Press>
              </View>
            ) : (
              <View className="flex-row gap-2">
                <View className="flex-1">
                  <Input
                    glass
                    placeholder="Promo code"
                    value={promo}
                    onChangeText={setPromo}
                    autoCapitalize="characters"
                    accessibilityLabel="Promo code"
                  />
                </View>
                <Button
                  variant="secondary"
                  disabled={promo.trim().length === 0}
                  onPress={() => {
                    const code = promo.trim();
                    haptics.success();
                    setAppliedPromo(code);
                    onApplyPromo?.(code);
                  }}
                >
                  Apply
                </Button>
              </View>
            )}

            <Card className="gap-0 overflow-hidden rounded-3xl border-0 bg-white p-5 dark:bg-[#1A1A20]">
              <SummaryRow label="Subtotal" value={fmt(subtotal, currency)} />
              {discount > 0 ? (
                <SummaryRow
                  label={`Promo (${appliedPromo})`}
                  value={`-${fmt(discount, currency)}`}
                  accent
                />
              ) : null}
              <SummaryRow
                label="Shipping"
                value={shipping === 0 ? "Free" : fmt(shipping, currency)}
              />
              <Separator className="my-2" />
              <SummaryRow label="Total" value={fmt(total, currency)} bold />
              {/* Delivery promise — the last reassurance before the CTA. */}
              <View className="mt-2.5 flex-row items-center gap-1.5">
                <Icon name="truck" size="sm" color="mutedForeground" />
                <Text variant="muted" className="text-xs">
                  Arrives{" "}
                  {new Date(Date.now() + 5 * 86400000).toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" })}{" "}
                  — free returns for 30 days
                </Text>
              </View>
            </Card>

            <Button
              haptic="heavy"
              onPress={() => onCheckout?.()}
              accessibilityLabel={`Checkout, total ${fmt(total, currency)}`}
              tone="inverse"
            >
              Checkout · {fmt(total, currency)}
            </Button>
          </StickyBottomBar>
        </>
      )}
    </SafeAreaView>
  );
}
agent prompt — paste into Claude Code, Cursor, Devin…
Recreate the "Cart Screen" screen in React Native using LumioUI.

Goal: Line items with thumbnails, quantity steppers and line totals, free-shipping progress with unlocked state, applied-promo row with live discount, and a shadowed sticky checkout bar Match the reference implementation's layout, behavior, and motion exactly — this is a production screen, not a mockup.

## Setup

Fastest path — install the screen plus its component deps:

```bash
npx lumioui add ecommerce-cart
```

Or pull just the components: `npx lumioui add button card empty-state icon image input progress separator stepper text`

Native packages:
- @lumioui/ui@latest
- @lumioui/core@latest
- react-native-safe-area-context@*
- react-native-reanimated@*
- react-native-svg@*
- nativewind@*

Stack assumptions: Expo SDK 54 / RN 0.81, Reanimated 4, NativeWind v4, TypeScript strict, Expo Go compatible.

## Components — how, where, when

### `button` — Tactile pill button with press spring, ink wash and async loading.
- `<Button>` ×3

### `card` — Compound surface — header, title, content and footer slots.
- `<Card>` ×1

### `empty-state` — Illustrated placeholder with title, copy and action slot.
- `<EmptyState>` ×1

### `icon` — Lucide icon set mapped to theme colors and sizes.
- `<Icon>` ×5

### `image` — Cached image with placeholder, fallback and aspect handling.
- `<Image>` ×1

### `input` — Labeled field with focus ring, error shake and success check.
- `<Input>` ×1

### `progress` — Animated fill bar with indeterminate sweep mode.
- `<Progress>` ×1

### `separator` — Hairline rule with orientation and spacing control.
- `<Separator>` ×1

### `stepper` — Plus/minus quantity control with hold-to-repeat.
- `<Stepper>` ×1

### `text` — Themed type scale from caption to display.
- `<Text>` ×14

## Conventions (non-negotiable)

- Styling via NativeWind `className` with semantic tokens only: `bg-background`, `text-foreground`, `bg-card`, `text-card-foreground`, `bg-secondary`, `bg-muted`, `text-muted-foreground`, `bg-accent`, `bg-destructive`, `border-border`. Hard-coded colors only inside media overlays (`text-white`, `bg-black/N`).
- Motion via the token presets from `@lumioui/core` (`spring.press`, `spring.surface`, `spring.glide`, `timing.fast`, `timing.slow`, …) — no bespoke spring configs or durations.
- Gate transform motion behind `useReducedMotion()`; keep opacity/color feedback, drop travel and overshoot.
- Haptics via `useHaptic()`: `light` on press, `selection` on toggle/select, `warning` on invalid input. Never import expo-haptics directly.
- Press feedback via `usePressAnimation()` (~0.97 scale), not `activeOpacity` tricks.
- a11y: `accessibilityRole` + `accessibilityLabel` on every control, touch targets ≥44pt.
- Entrance choreography: staggered `Mount` wrappers (delay 0/60/120…) so regions land in sequence instead of popping in at once.
- Wrap the screen in `SafeAreaView`; keep dark mode working via the semantic tokens.

## Deliverable

One file: `screens/ecommerce/cart.tsx` exporting CartScreen. Preserve the props interface — the host app wires the callbacks (navigation, submit handlers) through them.

## Reference implementation

The LumioUI source for this screen — reproduce it faithfully:

```tsx
import * as React from "react";
import { useState } from "react";
import { View, FlatList } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Button, Card, EmptyState, Glass, Icon, Image, Input, Progress, Separator, Stepper, Text } from "@lumioui/ui";
import { useHaptic } from "@lumioui/core";
import { catalogImg, LumioMark, Mount, Press, ROW_STAGGER, ROW_STAGGER_CAP } from "../shared";
import { StickyBottomBar } from "../shared";

export interface CartItem {
  id: string;
  name: string;
  brand?: string;
  variantLabel?: string;
  price: number;
  quantity: number;
  imageUrl?: string;
}

export interface CartScreenProps {
  items?: CartItem[];
  currency?: string;
  shippingCost?: number;
  /** Free shipping above this subtotal; set shippingCost for the fallback. */
  freeShippingThreshold?: number;
  onUpdateQty?: (item: CartItem, quantity: number) => void;
  onRemove?: (item: CartItem) => void;
  onApplyPromo?: (code: string) => void;
  onCheckout?: () => void;
  onBrowse?: () => void;
}

const img = (p: Parameters<typeof catalogImg>[0]) => catalogImg(p, 200, 250);

const DEFAULT_ITEMS: CartItem[] = [
  { id: "1", name: "Structured Wool Overcoat", brand: "Nord Standard", variantLabel: "Camel · M", price: 248, quantity: 1, imageUrl: img("overcoat") },
  { id: "3", name: "Leather Lace-Up Boots", brand: "Marchetti & Sons", variantLabel: "Black · 42", price: 295, quantity: 1, imageUrl: img("boots") },
  { id: "7", name: "Heritage Graphic Tee", brand: "Atelier M", variantLabel: "Indigo", price: 85, quantity: 2, imageUrl: img("graphicTee") },
];

/** Prices always render with two decimals ($248.00). */
const fmt = (n: number, currency = "$") =>
  `${currency}${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;

function SummaryRow({
  label,
  value,
  bold,
  accent,
}: {
  label: string;
  value: string;
  bold?: boolean;
  /** Success-green value — used for promo discounts. */
  accent?: boolean;
}) {
  return (
    <View className="flex-row items-center justify-between py-1">
      <Text variant={bold ? "large" : "muted"}>{label}</Text>
      <Text
        variant={bold ? "large" : "small"}
        className={accent ? "font-semibold tabular-nums text-success" : "tabular-nums"}
      >
        {value}
      </Text>
    </View>
  );
}

/** Bag — line items with thumbnails, qty steppers and remove affordances,
 *  promo field, order summary, and a sticky checkout bar. */
export function CartScreen({
  items = DEFAULT_ITEMS,
  currency = "$",
  shippingCost = 6.99,
  freeShippingThreshold = 150,
  onUpdateQty,
  onRemove,
  onApplyPromo,
  onCheckout,
  onBrowse,
}: CartScreenProps) {
  const haptics = useHaptic();
  const [promo, setPromo] = useState("");
  /** Code currently applied — swaps the promo field for a success row. */
  const [appliedPromo, setAppliedPromo] = useState<string | null>(null);

  const subtotal = items.reduce((sum, i) => sum + i.price * i.quantity, 0);
  const shipping = items.length === 0 || subtotal >= freeShippingThreshold ? 0 : shippingCost;
  // Template discount — a real host wires this to onApplyPromo's response.
  const discount = appliedPromo ? Math.round(subtotal * 0.1 * 100) / 100 : 0;
  const total = subtotal - discount + shipping;
  const remaining = Math.max(0, freeShippingThreshold - subtotal);
  const itemCount = items.reduce((sum, i) => sum + i.quantity, 0);

  return (
    <SafeAreaView className="flex-1 bg-[#F4F5F9] dark:bg-[#111114]" edges={["top"]}>
      <View className="px-6 pb-2 pt-4">
        <View className="flex-row items-center gap-2 pb-1.5">
          <LumioMark size={18} />
          <Text variant="muted" className="text-xs font-semibold uppercase tracking-widest">
            Lumio Market
          </Text>
        </View>
        <Text variant="h2">
          Bag{itemCount > 0 ? ` (${itemCount})` : ""}
        </Text>
      </View>

      {items.length === 0 ? (
        <View className="flex-1">
          <EmptyState
            title="Your bag is empty"
            description="Items you add will appear here."
            action={
              onBrowse ? (
                <Button variant="secondary" onPress={onBrowse}>
                  Continue shopping
                </Button>
              ) : undefined
            }
            className="flex-1"
          />
        </View>
      ) : (
        <>
          <FlatList
            data={items}
            keyExtractor={(item) => item.id}
            className="flex-1"
            contentContainerClassName="px-6 pb-56"
            contentInsetAdjustmentBehavior="automatic"
            showsVerticalScrollIndicator={false}
            ListHeaderComponent={
              <View
                className="mb-4 gap-2 rounded-2xl bg-[#EDF8C4] p-4 dark:bg-[#1E2410]"
                accessibilityRole="text"
                accessibilityLabel={
                  remaining > 0
                    ? `${fmt(remaining, currency)} away from free shipping`
                    : "Free shipping unlocked"
                }
              >
                {remaining > 0 ? (
                  <>
                    <Text variant="small" className="font-medium">
                      You're {fmt(remaining, currency)} away from{" "}
                      <Text variant="small" className="font-semibold text-[#65A30D]">
                        free shipping
                      </Text>
                    </Text>
                    <Progress value={(subtotal / freeShippingThreshold) * 100} className="h-1.5" />
                  </>
                ) : (
                  <View className="flex-row items-center gap-1.5">
                    <View className="h-5 w-5 items-center justify-center rounded-full bg-success">
                      <Icon name="check" size="sm" color="successForeground" />
                    </View>
                    <Text variant="small" className="font-medium text-success">
                      Free shipping unlocked
                    </Text>
                  </View>
                )}
              </View>
            }
            ItemSeparatorComponent={() => <View className="h-3" />}
            renderItem={({ item, index }) => (
              <Mount delay={Math.min(index * ROW_STAGGER, ROW_STAGGER_CAP)}>
                <View className="flex-row gap-3 overflow-hidden rounded-3xl bg-white p-3 dark:bg-[#1A1A20]">
                <Image
                  source={item.imageUrl ? { uri: item.imageUrl } : undefined}
                  rounded="xl"
                  aspectRatio={4 / 5}
                  className="w-24"
                  accessibilityLabel={`Photo of ${item.name}`}
                />
                <View className="flex-1">
                  <View className="flex-row items-start justify-between">
                    <View className="flex-1 gap-0.5 pr-2">
                      <Text variant="small" numberOfLines={1}>
                        {item.name}
                      </Text>
                      {item.brand || item.variantLabel ? (
                        <Text variant="muted" className="text-xs" numberOfLines={1}>
                          {[item.brand, item.variantLabel].filter(Boolean).join(" · ")}
                        </Text>
                      ) : null}
                    </View>
                    <Press
                      onPress={() => {
                        haptics.warning();
                        onRemove?.(item);
                      }}
                      accessibilityRole="button"
                      accessibilityLabel={`Remove ${item.name} from bag`}
                      hitSlop={12}
                      className="h-11 w-11 items-center justify-center"
                    >
                      <Icon name="trash" size="sm" color="mutedForeground" />
                    </Press>
                  </View>
                  <View className="flex-row items-center justify-between pt-2">
                    <Stepper
                      value={item.quantity}
                      min={0}
                      onValueChange={(qty) =>
                        qty === 0 ? onRemove?.(item) : onUpdateQty?.(item, qty)
                      }
                      accessibilityLabel={`Quantity for ${item.name}`}
                    />
                    <View className="items-end">
                      <Text
                        variant="small"
                        className="font-semibold tabular-nums"
                        accessibilityLabel={`${item.name} line total ${fmt(item.price * item.quantity, currency)}`}
                      >
                        {fmt(item.price * item.quantity, currency)}
                      </Text>
                      {item.quantity > 1 ? (
                        <Text variant="muted" className="text-xs tabular-nums">
                          {fmt(item.price, currency)} each
                        </Text>
                      ) : null}
                    </View>
                  </View>
                </View>
                </View>
              </Mount>
            )}
          />

          <StickyBottomBar className="gap-4">
            {appliedPromo ? (
              <View className="flex-row items-center justify-between rounded-2xl bg-success/10 py-1 pl-4">
                <View className="flex-1 flex-row items-center gap-2">
                  <Icon name="check-circle" size="sm" color="success" />
                  <Text variant="small">
                    Promo <Text variant="small" className="font-semibold">{appliedPromo}</Text> applied — 10% off
                  </Text>
                </View>
                <Press
                  onPress={() => {
                    haptics.light();
                    setAppliedPromo(null);
                    setPromo("");
                  }}
                  accessibilityRole="button"
                  accessibilityLabel={`Remove promo code ${appliedPromo}`}
                  hitSlop={12}
                  className="h-11 w-11 items-center justify-center"
                >
                  <Icon name="x" size="sm" color="mutedForeground" />
                </Press>
              </View>
            ) : (
              <View className="flex-row gap-2">
                <View className="flex-1">
                  <Input
                    glass
                    placeholder="Promo code"
                    value={promo}
                    onChangeText={setPromo}
                    autoCapitalize="characters"
                    accessibilityLabel="Promo code"
                  />
                </View>
                <Button
                  variant="secondary"
                  disabled={promo.trim().length === 0}
                  onPress={() => {
                    const code = promo.trim();
                    haptics.success();
                    setAppliedPromo(code);
                    onApplyPromo?.(code);
                  }}
                >
                  Apply
                </Button>
              </View>
            )}

            <Card className="gap-0 overflow-hidden rounded-3xl border-0 bg-white p-5 dark:bg-[#1A1A20]">
              <SummaryRow label="Subtotal" value={fmt(subtotal, currency)} />
              {discount > 0 ? (
                <SummaryRow
                  label={`Promo (${appliedPromo})`}
                  value={`-${fmt(discount, currency)}`}
                  accent
                />
              ) : null}
              <SummaryRow
                label="Shipping"
                value={shipping === 0 ? "Free" : fmt(shipping, currency)}
              />
              <Separator className="my-2" />
              <SummaryRow label="Total" value={fmt(total, currency)} bold />
              {/* Delivery promise — the last reassurance before the CTA. */}
              <View className="mt-2.5 flex-row items-center gap-1.5">
                <Icon name="truck" size="sm" color="mutedForeground" />
                <Text variant="muted" className="text-xs">
                  Arrives{" "}
                  {new Date(Date.now() + 5 * 86400000).toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" })}{" "}
                  — free returns for 30 days
                </Text>
              </View>
            </Card>

            <Button
              haptic="heavy"
              onPress={() => onCheckout?.()}
              accessibilityLabel={`Checkout, total ${fmt(total, currency)}`}
              tone="inverse"
            >
              Checkout · {fmt(total, currency)}
            </Button>
          </StickyBottomBar>
        </>
      )}
    </SafeAreaView>
  );
}
```

Screen: https://lumioui.dev/screens/ecommerce-cart
iPhone 17 · iOS
9:41
Live preview — the real screen via Expo Web · open full ↗