Zum Inhalt springen
    WingoUI
    KomponentenVorlagenDokuPreiseKI-AgentenBlogChangelog
    1. Startseite
    2. Blog
    3. Praxis: Screens und Apps
    4. React Checkout Page: Warenkorb, Schritte, Gutscheine
    Read in English
    1. Blog
    2. Praxis: Screens und Apps
    3. React Checkout Page: Warenkorb, Schritte, Gutscheine
    Praxis: Screens und Apps
    Praxis: Screens und Apps

    React Checkout Page: Warenkorb, Schritte, Gutscheine

    SR
    Serban Rusu · Gründer von Wingo UI
    16. Okt. 2026 · 12 Min. Lesezeit

    Auf dieser Seite

    0%
    1. Was braucht eine React Checkout Page?
    2. Wie binde ich die Checkout Page in Next.js ein?
    3. Wie sollte ein React Warenkorb mit Mengen und Gutscheinen umgehen?
    4. Wie nutze ich React Checkout Steps ohne die ganze Seite?
    5. Welche Formulardetails senken die Abbruchrate im Checkout?
    6. Wie bleibt der Bezahl-Button in einer Mobile Checkout UI erreichbar?
    7. Brauche ich neben der Warenkorbseite auch einen Cart Drawer?
    8. Wie probiere ich die Checkout Page aus, bevor ich sie anschließe?
    9. Häufige Fragen
      1. Wie viele Schritte sollte ein Checkout haben?
      2. Sollte eine React Checkout Page ein Kundenkonto erzwingen?
      3. Wo sollten Gutscheincodes geprüft werden?
      4. Wie bleibt der Bezahl-Button im mobilen Checkout sichtbar?
      5. Kann der Browser ein eigenes Checkout-Formular in React automatisch ausfüllen?

    Kurz gesagt

    Eine React Checkout Page braucht drei Stufen: einen Warenkorb, in dem Käufer Mengen und Gutscheine ändern und die Summe immer sehen, vier Checkout-Schritte (Adresse, Lieferung, Zahlung, Prüfung), die Schritt für Schritt validieren und bei Zurück alle Daten behalten, und eine Bestätigung, die den Fokus bekommt. Auf dem Smartphone gehören die Summe und der Hauptbutton in eine Sticky Bar über dem Home-Indikator, die sich bei offener Tastatur versteckt. Der Block Checkout Page von Wingo UI erledigt all das; du schließt nur die Warenkorb-API, die Gutscheinprüfung und den Bestellaufruf an.

    Veröffentlicht am 16. Okt. 2026

    Die meisten Tutorials zur React Checkout Page hören bei einem Formular mit Bezahl-Button auf. Die eigentliche Arbeit liegt drumherum: ein Warenkorb, in dem du Mengen änderst, ohne die Summe zu verlieren, Gutscheincodes, die nicht mehr greifen, wenn der Warenkorb schrumpft, ein Schritt, der scheitert, ohne das Eingetippte zu löschen, und ein Bezahl-Button, der am Smartphone unter dem Daumen bleibt. Dieses Tutorial baut die ganze Checkout-Seite vom Warenkorb bis zur Bestätigung mit dem Block Checkout Page von Wingo UI, der Bibliothek, die wir bauen. Es zeigt auch die kleineren Teile, die du einzeln nutzen kannst.

    Was braucht eine React Checkout Page?

    Eine React Checkout Page braucht drei Stufen und eine kurze Liste von Details, die entscheiden, ob Leute den Kauf abschließen. Die Stufen:

    1. Warenkorb. Positionen mit Mengen-Steppern, Entfernen mit Rückgängig, ein Balken bis zur kostenlosen Lieferung, ein Gutscheinfeld und eine Übersicht, deren Summe sich bei jeder Änderung aktualisiert.
    2. Checkout-Schritte. Adresse, Lieferung, Zahlung und Prüfung. Jeder Schritt validiert bei Weiter, Zurück behält alles, und der Prüfschritt gibt die Bestellung auf und zeigt eine Ablehnung direkt an Ort und Stelle.
    3. Bestätigung. Die Bestellnummer, die E-Mail-Adresse, an die die Bestätigung ging, sowie die Buttons Track order und Continue shopping. Der Fokus springt auf die Überschrift, damit ein Screenreader sie vorliest.

    Die Details stammen aus Studien dazu, warum Leute abspringen. Die Seite des Baymard Institute zu Kaufabbrüchen (opens in a new tab) (aktualisiert am 22. September 2025) listet die häufigsten Gründe, die Käufer in Baymards Umfrage für einen abgebrochenen Checkout nannten: zu hohe Zusatzkosten (40 %), langsame Lieferung (20 %), kein Vertrauen, dem Shop Kartendaten zu geben (19 %), Pflicht zum Kundenkonto (18 %) und ein zu langer oder komplizierter Checkout (17 %). Weitere 12 % konnten die Gesamtsumme vorab nicht sehen oder berechnen. Vier davon sind UI-Probleme: Zeig Summe und Lieferkosten früh, erlaube Gast-Checkout, halte das Formular kurz und lass den Zahlungsschritt vertrauenswürdig aussehen.

    Baymards Checkout-Benchmark (opens in a new tab) (veröffentlicht am 26. Juni 2024) kommt im Schnitt auf 5,1 Schritte und 11,3 Formularfelder, und laut Baymard brauchen die meisten Shops nur 8 Felder. Felder kosten mehr als Schritte. Deshalb hat der Checkout unten ein einziges Namensfeld statt Vor- und Nachname und versteckt die Firmenfelder hinter einem Schalter.

    Installier den Block. Die CLI holt Warenkorb, Schritte, Gutscheinfeld, Sticky Bar und Order Tracker, aus denen er besteht, gleich mit:

    bash
    npx wingo-ui@latest add checkout-page
    Ändere eine Menge und sieh zu, wie der Balken für kostenlose Lieferung und die Summe mitgehen, löse FALL15 im Gutscheinfeld ein und drück dann Continue to delivery, um die vier Schritte durchzugehen. Scroll auf einem schmalen Bildschirm an der Übersicht vorbei, dann gleitet die Sticky Bar mit der Summe von unten herein
    $ npx wingo-ui@latest add checkout-page
    ProDoku: Checkout Page

    Wie binde ich die Checkout Page in Next.js ein?

    Rendere CheckoutPage in einer Client-Komponente und gib ihr vier Callbacks: einen, der den Warenkorb speichert, einen, der einen Gutschein auf dem Server prüft, einen, der die Bestellung aufgibt, und einen, der die Bestellung öffnet. Der Block kümmert sich um die Stufen, den Zustand der Schritte, den Fokus und die Übergänge. Er funktioniert als Checkout Page Template mit einem einzigen Import: Jeder Text steckt in labels, jede visuelle Entscheidung ist ein Prop.

    Vorher ein Stolperstein. Die Checkout-Schritte im Block nutzen standardmäßig rumänische Regeln (6-stellige Postleitzahlen, die Prüfung der Steuer-ID CUI, Telefonnummern mit +40), und der Block hat keinen eigenen Country-Prop. Eine US-Postleitzahl wie 94103 scheitert dann im Adressschritt mit „The postal code has 6 digits“. Mit einem nicht rumänischen locale tauscht der Block die Auswahl der Kreise zwar schon gegen US-Bundesstaaten, die Länderregeln bleiben aber. Setz das Land einmal über die Defaults von UIProvider, die die Schritte für jeden Prop lesen, den der Block nicht setzt, und benenne die Firmenfelder über labels.checkout um:

    tsx
    // app/(shop)/cart/checkout.tsx
    "use client";
    import { useRouter } from "next/navigation";
    import { CheckoutPage, type CheckoutPageProps } from "@/components/blocks/checkout-page";
    import type { CouponResult } from "@/components/ui/coupon-input";
    import { UIProvider } from "@/lib/ui-config";
    // US-Postleitzahlen und -Telefonnummern im Adressschritt; Steuer-IDs werden nicht mehr als rumänische CUIs geprüft
    const US_CHECKOUT = { "checkout-steps": { country: "US" } };
    // die Firmenfelder behalten ihre rumänischen Namen und Platzhalter, bis du sie umbenennst
    const US_COMPANY_LABELS = {
    cui: "Steuernummer (EIN)",
    cuiPlaceholder: "12-3456789",
    regCom: "Registernummer des Bundesstaats (optional)",
    regComPlaceholder: "",
    };
    type ShopData = Pick<
    CheckoutPageProps,
    "defaultItems" | "coupons" | "savedAddresses" | "deliveryMethods" | "lockers" | "paymentMethods" | "customerEmail" | "guest"
    >;
    export function Checkout(shop: ShopData) {
    const router = useRouter();
    return (
    <UIProvider defaults={US_CHECKOUT}>
    <CheckoutPage
    {...shop}
    recommended={[]}
    currency="USD"
    locale="en-US"
    freeShippingThreshold={250}
    ctaTone="brand"
    labels={{ checkout: US_COMPANY_LABELS }}
    onItemsChange={(items) =>
    fetch("/api/cart", { method: "PUT", body: JSON.stringify(items) })
    }
    onApplyCoupon={async (code): Promise<CouponResult> => {
    const response = await fetch("/api/coupons/check", {
    method: "POST",
    body: JSON.stringify({ code }),
    });
    if (!response.ok) return { ok: false, error: "Diesen Code kennen wir nicht" };
    return (await response.json()) as CouponResult;
    }}
    onPlaceOrder={async (value, totals) => {
    const response = await fetch("/api/orders", {
    method: "POST",
    body: JSON.stringify({ value, total: totals.total }),
    });
    // die Meldung erscheint im Prüfschritt, über Place order
    if (!response.ok) throw new Error(await response.text());
    return (await response.json()) as { orderNumber: string };
    }}
    onTrackOrder={(orderNumber) => router.push(`/account/orders/${orderNumber}`)}
    onContinueShopping={() => router.push("/")}
    />
    </UIProvider>
    );
    }

    Die Seite selbst bleibt eine Server-Komponente, die die Daten lädt. getCart und getShop stehen für deine eigene Datenschicht:

    tsx
    // app/(shop)/cart/page.tsx
    import { getCart, getShop } from "@/lib/shop";
    import { Checkout } from "./checkout";
    export default async function CartPage() {
    const [cart, shop] = await Promise.all([getCart(), getShop()]);
    return (
    <Checkout
    defaultItems={cart.items}
    coupons={shop.coupons}
    savedAddresses={cart.customer?.addresses ?? []}
    deliveryMethods={shop.deliveryMethods}
    lockers={shop.lockers}
    paymentMethods={shop.paymentMethods}
    customerEmail={cart.customer?.email}
    guest={!cart.customer}
    />
    );
    }

    Jeder Daten-Prop, den du weglässt, fällt auf den Beispielshop des Blocks zurück (auch die E-Mail des Beispielkäufers, wenn customerEmail undefined ist). Deshalb übergibt der Wrapper den Warenkorb, die Gutscheine, die Adressen, die Liefermethoden mit ihren Packstationen, die Zahlungsmethoden und eine leere recommended-Leiste. Nimm items mit onItemsChange statt defaultItems, wenn noch ein anderer Teil der Seite den Warenkorb liest, etwa ein Badge im Header. Jede Position ist ein CartItem: id, title, price, quantity und optional compareAtPrice, maxQuantity, taxRate, image, variant und href. guest lässt die gespeicherten Adressen und die vorausgefüllte E-Mail weg und ergänzt auf der Bestätigung einen Hinweis zum Anlegen eines Kontos. So fragt niemand vorab nach einem Konto. Save for later bestätigt sich selbst mit einem Toast, also mounte <Toaster /> aus @/components/ui/toast einmal im Layout des Shops.

    Zwei Regeln für die Serverseite. Erstens: Berechne die Summe aus deinen eigenen Preisen neu und vertrau nie totals.total aus dem Browser. Der Block summiert den Warenkorb mit computeCartTotals aus @/lib/commerce, einem einfachen Modul ohne "use client". Ein Route Handler kann also dieselbe Rechnung ausführen und eine Bestellung ablehnen, deren Summe sich verschoben hat:

    ts
    // app/api/orders/route.ts
    import { computeCartTotals } from "@/lib/commerce";
    import type { CheckoutValue } from "@/components/ui/checkout-steps";
    import { getCart, getShop } from "@/lib/shop";
    export async function POST(request: Request) {
    const { value, total } = (await request.json()) as { value: CheckoutValue; total: number };
    const [cart, shop] = await Promise.all([getCart(), getShop()]);
    const method = shop.deliveryMethods.find((m) => m.id === value.deliveryMethod);
    const payment = shop.paymentMethods.find((m) => m.id === value.paymentMethod);
    // eingelöste Codes in der Reihenfolge, in der der Käufer sie hinzugefügt hat, nachgeschlagen in deinem eigenen Katalog
    const coupons = (value.coupons ?? []).flatMap((applied) => shop.coupons.filter((c) => c.code === applied.code));
    const totals = computeCartTotals(cart.items, {
    coupons,
    shipping: method?.price ?? null,
    // die Regel der Schritte: freeFrom einer Methode hat Vorrang, Abholung bekommt nie den Schwellenwert
    freeShippingThreshold: method?.freeFrom ?? (method?.type === "pickup" ? 0 : 250),
    });
    const expected = totals.total + (payment?.fee ?? 0);
    if (Math.abs(expected - total) > 0.005) {
    return new Response("Die Preise haben sich während des Checkouts geändert. Prüf die Summe und gib die Bestellung erneut auf.", { status: 409 });
    }
    // `expected` belasten, die Bestellung speichern und mit ihrer Nummer antworten
    return Response.json({ orderNumber: "1043" });
    }

    Zweitens: Bleib in der Antwort ehrlich. Eine abgelehnte Karte sollte eine Antwort mit einem Status außerhalb von 2xx sein, mit einer Meldung, mit der der Käufer etwas anfangen kann. Denn genau diese Meldung zeigt der Block über Place order an und lässt den Prüfschritt offen.

    Wie sollte ein React Warenkorb mit Mengen und Gutscheinen umgehen?

    Jede Änderung am Warenkorb bewegt die Summe, und keine Aktion des Käufers geht verloren, ohne dass er sie zurückholen kann. Die Warenkorb-Stufe folgt vier Regeln:

    • Jede Änderung bewegt die Übersicht. Stepper, Entfernen und Gutscheine aktualisieren Zwischensumme, Ersparnis-Badge, Lieferschätzung und Summe sofort an Ort und Stelle. So fragt sich niemand, was er am Ende zahlt.
    • Entfernen lässt sich rückgängig machen. Das × (oder ein Wisch nach links auf Touch-Geräten) klappt die Position zu einer Zeile mit Undo und einem Countdown von fünf Sekunden zusammen. Save for later verschiebt eine Position in eine Liste unter dem Warenkorb, statt sie zu löschen.
    • Kostenlose Lieferung ist sichtbar. Ein Balken zeigt, wie viel noch fehlt, und wird an der Schwelle grün. Das beantwortet die Frage nach den Zusatzkosten, bevor der Checkout beginnt.
    • Gutscheine werden neu geprüft. Ein Code mit Mindestbestellwert wird erneut geprüft, sobald sich eine Menge ändert. Greift er nicht mehr, entfernt der Block ihn und sagt, warum, statt einen Rabatt zu zeigen, den der Server ablehnen wird.

    Das Gutscheinfeld ist der Coupon Input, und der funktioniert auch allein in jedem Warenkorb. Gib ihm einen lokalen Katalog für sofortige Prüfungen oder onApply für eine Prüfung auf dem Server, die { ok: true, coupon } oder { ok: false, error } zurückgibt:

    tsx
    "use client";
    import { useState } from "react";
    import { CouponInput, type AppliedCoupon } from "@/components/ui/coupon-input";
    import type { Coupon } from "@/lib/commerce";
    const COUPONS: Coupon[] = [
    { code: "FALL15", type: "percent", value: 15, maxDiscount: 150, label: "Herbstaktion" },
    { code: "FREESHIP", type: "shipping", value: 0, label: "Kostenlose Lieferung" },
    { code: "SAVE50", type: "fixed", value: 50, minSubtotal: 300 },
    ];
    export function CartCoupon({ subtotal, shipping }: { subtotal: number; shipping: number }) {
    const [applied, setApplied] = useState<AppliedCoupon[]>([]);
    return (
    <CouponInput
    value={applied}
    onValueChange={setApplied}
    coupons={COUPONS}
    subtotal={subtotal}
    shipping={shipping}
    locale="en-US"
    />
    );
    }

    Übergib bei jedem Rendern die Zwischensumme der Positionen als subtotal und die aktuellen Lieferkosten als shipping, in denselben Währungseinheiten wie die Gutscheinwerte (Dollar, nicht Cent). Gegen diese Werte wird ein Code aus dem Katalog geprüft, und wenn sie sich ändern, rechnen eingelöste Codes ihre Ersparnis neu aus. Ein Code, der nicht mehr greift, wird bernsteinfarben und bekommt einen Hinweis darunter. So sieht der Käufer, warum der Rabatt weg ist, bevor der Server ihn ablehnt.

    Öffne „Have a promo code?“, tipp save50 und drück Enter: Der Code wird in Großbuchstaben umgewandelt, geprüft und landet als Chip. Entferne ihn und probier big200, um zu sehen, wie viel dem Warenkorb noch fehlt
    $ npx wingo-ui@latest add coupon-input
    ProDoku: Coupon input

    Enter im Gutscheinfeld sendet nie das Checkout-Formular drumherum ab. Das ist ein häufiger Bug, wenn das Codefeld in einem <form> sitzt. Escape leert den getippten Code, ein zweites Escape schließt das Feld und gibt den Fokus an den Button „Have a promo code?“ zurück.

    Wie nutze ich React Checkout Steps ohne die ganze Seite?

    Nimm Checkout Steps direkt, wenn der Warenkorb woanders liegt, etwa in einem Drawer, oder wenn du etwas ohne Lieferung verkaufst. Die meisten Stepper-Beispiele hören bei drei Formularen hinter Weiter-Buttons auf. Das hier ist derselbe Ablauf in vier Schritten, den auch der Block nutzt, mit eingebauter Validierung, gespeicherten Adressen, Packstationen und Zahlungsgebühren. Daneben steht eine Übersicht, die auf breiten Bildschirmen eine Sticky-Spalte ist und auf schmalen eine Leiste mit Show summary. Standardmäßig gelten rumänische Regeln (country="RO", RON), also setz für einen US-Shop Land, Währung und Locale:

    tsx
    "use client";
    import { CheckoutSteps, type DeliveryMethod, type PaymentMethod } from "@/components/ui/checkout-steps";
    import type { CartItem } from "@/components/ui/cart-drawer";
    import type { Coupon } from "@/lib/commerce";
    const STATES = [
    { value: "CA", label: "California" },
    { value: "NY", label: "New York" },
    { value: "TX", label: "Texas" },
    ];
    const DELIVERY: DeliveryMethod[] = [
    { id: "ups", label: "UPS Ground", price: 19.99, eta: "3-5 Werktage", type: "courier" },
    { id: "pickup", label: "Abholung in New York", price: 0, type: "pickup" },
    ];
    // hier das Kartenelement deines Zahlungsanbieters mounten, damit Kartendaten nie deinen Server berühren
    function CardFields() {
    return <div id="card-fields" className="min-h-24" />;
    }
    const PAYMENT: PaymentMethod[] = [
    { id: "card", label: "Karte", details: <CardFields /> },
    { id: "transfer", label: "Überweisung" },
    ];
    export function Checkout({ items, coupons }: { items: CartItem[]; coupons: Coupon[] }) {
    return (
    <CheckoutSteps
    items={items}
    couponProps={{ coupons }}
    country="US"
    currency="USD"
    locale="en-US"
    regions={STATES}
    deliveryMethods={DELIVERY}
    paymentMethods={PAYMENT}
    invoice={false}
    freeShippingThreshold={250}
    tone="brand"
    labels={{
    county: "Bundesstaat",
    countyPlaceholder: "Bundesstaat wählen",
    placeOrder: (total) => `${total} bezahlen`,
    }}
    onPlaceOrder={(value, totals) =>
    fetch("/api/orders", { method: "POST", body: JSON.stringify({ value, total: totals.total }) }).then(
    async (response) => {
    if (!response.ok) throw new Error(await response.text());
    },
    )
    }
    successContent={<p>Danke, deine Bestellung ist aufgegeben. Die Quittung haben wir dir per E-Mail geschickt.</p>}
    />
    );
    }

    Für ein digitales Produkt übergibst du steps={["payment", "review"]} und lässt deliveryMethods weg. Ein Haken dabei: Das E-Mail-Feld sitzt im Adressschritt, ohne diesen Schritt fragt also niemand die E-Mail ab. Nimm sie aus dem angemeldeten Konto oder frag sie ab, bevor sich der Checkout öffnet. Eine Zahlungsmethode mit fee fügt der Übersicht eine Zeile hinzu und erhöht die Summe, und eine mit minTotal bleibt mit dem Hinweis Available from deaktiviert, bis der Warenkorb den Betrag erreicht. Die Feldprüfungen sind als validateCheckoutStep(step, value, options) exportiert. Die Funktion gibt Fehler mit Schlüsseln wie "address.phone" oder "invoice.cui" zurück. Sie liegt in einer Datei mit "use client", also ruf sie aus Client-Code auf (ein eigener Ablauf, ein Unit-Test) und validiere den abgeschickten Wert auf dem Server mit deinem eigenen Schema.

    Die Mechanik ist dieselbe wie bei jedem Wizard: ein Wertobjekt, Validierung pro Schritt, Fokus auf das erste ungültige Feld und Übergänge, die der Richtung folgen. Wenn du dieses Muster für ein Formular brauchst, das kein Checkout ist: Das Tutorial zum React Multi Step Form baut es mit react-hook-form und zod.

    Welche Formulardetails senken die Abbruchrate im Checkout?

    Am meisten bringen Autofill, weniger Felder, Fehler, die das Eingetippte behalten, eine nützliche Enter-Taste, eine früh sichtbare Summe und Gast-Checkout. Die Schritte bringen jedes davon mit:

    • Lass den Browser das Formular füllen. Jedes Adressfeld trägt seinen autocomplete-Wert (email, name, tel, address-line1, address-line2, address-level1, address-level2, postal-code, organization), so füllt ein Tipp auf eine gespeicherte Adresse den ganzen Schritt. Kartenfelder sollten aus dem gehosteten Element deines Zahlungsanbieters kommen, damit Kartendaten nie deinen Server berühren.
    • Frag weniger ab. Ein Namensfeld, die Firmenfelder erst nach dem Schalter Company und Notizen nur optional.
    • Scheitern, ohne etwas zu verlieren. Weiter markiert die fehlerhaften Felder, schüttelt den Button und fokussiert das erste davon. Zurück validiert nie, und eine abgelehnte Zahlung lässt den Prüfschritt und jede Auswahl unangetastet.
    • Mach Enter nützlich. Am Smartphone springt die Aktionstaste der Tastatur ins nächste Feld und löst im letzten Feld Weiter aus. So sucht der Käufer keinen Button hinter der Tastatur.
    • Zeig die Summe früh. Die Übersicht mit Lieferung und einer etwaigen Zahlungsgebühr ist in jedem Schritt sichtbar, und der Button Place order trägt den Endbetrag.
    • Erzwing kein Konto. Zuerst der Gast-Checkout, das Angebot für ein Konto auf der Bestätigung.

    Wie bleibt der Bezahl-Button in einer Mobile Checkout UI erreichbar?

    Am Smartphone scrollt der Weiter-Button der Übersicht schnell aus dem Bild. Dazu kommt, worauf Stripes Guide zum mobilen Checkout (opens in a new tab) (aktualisiert am 8. Juli 2025) hinweist: Sobald ein Feld den Fokus hat, belegt die Bildschirmtastatur fast die Hälfte des Bildschirms. Eine gute Mobile Checkout UI löst beides: Eine Leiste mit Summe und Aktion erscheint, solange der Button der Seite außerhalb des Bildschirms liegt, sitzt über dem Home-Indikator und geht aus dem Weg, solange die Tastatur offen ist.

    Unter 1024px löst der Block das in der Warenkorb-Stufe mit der Sticky CTA Bar. In den Checkout-Schritten ist am Smartphone jeder Schritt eine Seite in voller Höhe mit einer eigenen Fußleiste für Summe, Zurück und Weiter, die bei offener Tastatur verschwindet. Um die Leiste auf deiner eigenen Produkt- oder Warenkorbseite zu nutzen, richte sie auf den Button in der Seite:

    tsx
    "use client";
    import { useRef } from "react";
    import { ArrowRight } from "lucide-react";
    import { Button } from "@/components/ui/button";
    import { StickyCtaBar } from "@/components/ui/sticky-cta-bar";
    export function CartFooter({ total, onContinue }: { total: number; onContinue: () => Promise<void> }) {
    const continueRef = useRef<HTMLButtonElement>(null);
    return (
    <>
    <Button ref={continueRef} tone="brand" onClick={onContinue}>
    Weiter zur Lieferung
    </Button>
    <StickyCtaBar
    price={total}
    currency="USD"
    locale="en-US"
    caption="Inkl. Steuern · Kostenlose Lieferung"
    actionLabel="Weiter"
    actionIcon={<ArrowRight />}
    tone="brand"
    onAction={onContinue}
    showOn="anchor"
    anchorRef={continueRef}
    hideFrom="lg"
    />
    </>
    );
    }
    Scroll im Smartphone-Rahmen: Die Leiste gleitet herein, sobald der Button der Seite den Bildschirm verlässt, und verschwindet, wenn er zurückkommt. Tipp auf die Aktion, dann siehst du, dass der Spinner die Breite des Buttons hält
    $ npx wingo-ui@latest add sticky-cta-bar
    ProDoku: Sticky CTA Bar

    keyboard="hide" ist der Default, deshalb liegt die Leiste nie über einem fokussierten Gutscheinfeld. safeArea hält Abstand zum Home-Indikator, und die Leiste schreibt ihre Höhe in --cta-bar-offset, damit ein schwebender Button darüber sitzen kann. Die Hintergründe zu beidem stehen in unseren Beiträgen zu Safe Area Insets in Tailwind und zu position fixed unter der Tastatur von iOS Safari.

    Brauche ich neben der Warenkorbseite auch einen Cart Drawer?

    Ja, wenn Käufer auf Produktseiten Artikel in den Warenkorb legen: Sie wollen ihn über den Header prüfen, ohne die Seite zu verlassen. Der Cart Drawer ist am Desktop ein Seitenpanel und am Smartphone ein Bottom Sheet, mit demselben Rückgängig, demselben Balken für kostenlose Lieferung, demselben Gutscheinfeld und derselben Übersicht wie die Warenkorb-Stufe. Richte seinen Button mit checkoutHref="/cart" auf den Checkout. Solange beide dieselbe Warenkorb-API lesen, landet der Käufer in der Warenkorb-Stufe des Blocks, mit denselben Positionen.

    Wie probiere ich die Checkout Page aus, bevor ich sie anschließe?

    Starte mit der Doku zur Checkout Page. Im Playground öffnest du sie auf jeder Stufe und in jedem Schritt, wechselst zu einem Gastkäufer, änderst die Schwelle für kostenlose Lieferung und den tone und probierst die abgelehnte Karte mit der Endung 0002. Der Block und die Commerce-Komponenten aus diesem Beitrag gehören zu Wingo UI Pro. Pro kostet Stand Oktober 2026 $8 im Monat, $80 im Jahr oder $150 für lebenslangen Zugang (Preise). Für die anderen Screens, die ein Produkt braucht, lies das Tutorial zum Next.js Admin Dashboard und die übrigen Build-Tutorials.

    Komponenten in diesem Artikel

    • Checkout Page

      A React checkout page from the cart to Order placed: an editable cart, four checkout steps and the order confirmation.

      Pro
    • Checkout steps

      A React multi-step checkout: address and invoice, delivery with lockers, payment and a review that places the order, beside a summary.

      Pro
    • Cart drawer

      A React shopping cart drawer: a side panel on desktop, a bottom sheet on phones, with undo, free delivery progress, a promo code and totals.

      Pro
    • Coupon input

      A React coupon code input for carts and checkouts: a link that opens it, Apply inside the box, async checks and applied codes as chips.

      Pro
    • Sticky CTA Bar

      A React sticky CTA bar for product, listing and checkout pages on phones: the price on one side, the main action on the other.

      Pro

    Häufige Fragen

    Wie viele Schritte sollte ein Checkout haben?

    Weniger Felder bringen mehr als weniger Schritte. Baymards Benchmark von 2024 ermittelte im Schnitt 5,1 Schritte und 11,3 Formularfelder, und laut Baymard brauchen die meisten Shops nur 8 Felder. Streich also zuerst Felder und verteil den Rest auf Adresse, Lieferung, Zahlung und Prüfung.

    Sollte eine React Checkout Page ein Kundenkonto erzwingen?

    Nein. In Baymards Umfrage zu Kaufabbrüchen (Seite im September 2025 aktualisiert) nannten 18 % der Käufer, die einen Checkout abgebrochen hatten, als Grund, dass der Shop ein Konto verlangte. Biete einen Gast-Checkout an und schlag das Konto erst auf der Bestätigungsseite vor, wenn die Bestellung durch ist.

    Wo sollten Gutscheincodes geprüft werden?

    An zwei Stellen: im Client für sofortiges Feedback, wenn jemand einen Code einlöst, und auf dem Server, wenn die Bestellung aufgegeben wird. Prüf eingelöste Codes bei jeder Änderung am Warenkorb neu, denn ein Code mit Mindestbestellwert kann nicht mehr greifen, sobald eine Menge sinkt.

    Wie bleibt der Bezahl-Button im mobilen Checkout sichtbar?

    Rendere unten eine fixierte Leiste mit der Summe und der Hauptaktion und gib ihr den Safe-Area-Abstand, damit sie über dem Home-Indikator sitzt. Zeig sie nur, solange der Button der Seite selbst außerhalb des Bildschirms liegt, und versteck sie bei offener Bildschirmtastatur, damit sie nie das fokussierte Feld verdeckt.

    Kann der Browser ein eigenes Checkout-Formular in React automatisch ausfüllen?

    Ja, solange jedes Feld ein echtes input-Element mit dem passenden autocomplete-Wert ist, etwa name, email, tel, address-line1, address-level2, postal-code und die cc-Werte für Karten. Felder aus div-Elementen oder Inputs mit abgeschaltetem autocomplete machen Autofill kaputt.

    • E-Commerce
    • Checkout
    • Formulare
    • Mobile UI
    • Next.js

    Teilen

    SR

    Über den Autor

    Serban Rusu

    Gründer von Wingo UI

    Serban Rusu hat Wingo UI gegründet. Er baut die Komponenten, die CLI und den MCP-Server und schreibt über React-UIs für Smartphones und KI-Agents.

    Mehr von Serban
    Weiterlesen

    Ähnliche Artikel

    Alle Artikel
    Mobile UI mit React

    React Floating Action Button: Speed Dial und Safe Areas

    Ein React Floating Action Button für den Daumen: Speed Dial mit 2 bis 6 Aktionen, einklappendes Label und Platz über Tab-Leiste und Home-Indikator.

    SRSerban Rusu·16. Okt. 2026·9 Min. Lesezeit
    Komponenten-Guides

    React Stepper: Zustände, Varianten und Smartphones

    Ein React Stepper für Checkout, Onboarding und KYC: Kreise, Balken oder Punkte, was jeder Schrittzustand bedeutet und wie er auf dem Handy kompakt wird.

    SRSerban Rusu·13. Okt. 2026·8 Min. Lesezeit
    Mobile UI mit React

    Mobile First React: 9 Regeln für Komponenten mit Code

    Mobile First React Regel für Regel, mit Code: Bottom Sheets statt Popover, 44px Touch-Ziele, Safe Areas, dvh, Leisten über der Tastatur, Tabellen als Karten.

    SRSerban Rusu·9. Okt. 2026·21 Min. Lesezeit
    Newsletter

    Neue Artikel per E-Mail

    Neue Guides, Tutorials und Vergleiche aus dem Wingo UI Blog, sobald sie erscheinen. Die meisten Artikel sind auf Englisch.

    Kein Spam. Du kannst dich jederzeit abmelden.

    WingoUI

    Animierte, konfigurierbare, mobile-first React-Komponenten und Blöcke. Kopier den Quellcode, pass ihn an und lass deinen Coding-Agenten damit bauen.

    Produkt

    • Komponenten
    • Vorlagen
    • Preise
    • KI-Agenten
    • Theme-Generator

    Ressourcen

    • Doku
    • Blog
    • Changelog
    • Changelog-RSS
    • llms.txt

    Rechtliches

    • AGB
    • Datenschutz
    • Rückerstattung
    • Lizenz
    • Impressum

    Komponenten-Kategorien

    • Buttons & Actions
    • Inputs
    • Forms
    • Navigation
    • Overlays
    • Feedback
    • Data Display
    • Tables & Lists
    • Charts & Stats
    • Layout
    • Media
    • AI Kit
    • Text & Effects
    • Mobile
    • Commerce
    • Marketing Sections
    • Blocks
    • Hooks & Utilities

    © 2026 Wingo UI

    Wingo UI