# Checkout

> Guide contact, delivery, payment, review, and confirmation.

- Category: Commerce
- Kind: block
- Page: https://uaiblocks.dev/components/checkout
- Registry item: https://uaiblocks.dev/r/checkout.json
- Source: https://github.com/victor-teles/uai/blob/main/src/registry/uai/blocks/checkout.tsx

## Install

```bash
bunx shadcn@latest add https://uaiblocks.dev/r/checkout.json
```

## Usage

```tsx
"use client";

import { useState } from "react";
import {
  Checkout,
  CheckoutConfirmation,
  CheckoutConfirmationTitle,
  CheckoutDescription,
  CheckoutError,
  CheckoutFieldRow,
  CheckoutHeader,
  CheckoutMain,
  CheckoutPayment,
  CheckoutPaymentNote,
  CheckoutPlaceOrder,
  CheckoutProgress,
  CheckoutProgressStep,
  CheckoutSection,
  CheckoutSectionContinue,
  CheckoutSectionEdit,
  CheckoutSectionForm,
  CheckoutSectionHeader,
  CheckoutSectionSummary,
  CheckoutSectionTitle,
  CheckoutSummary,
  CheckoutSummaryTotals,
  CheckoutTitle,
  type CheckoutVariant,
} from "@/components/uai/checkout";
import { FormField, FormFieldInput, FormFieldLabel } from "@/components/ui/uai/form-field";
import {
  PriceSummaryHeader,
  PriceSummaryItem,
  PriceSummaryList,
  PriceSummaryTitle,
  PriceSummaryTotal,
} from "@/components/ui/uai/price-summary";
import { StatusBannerDescription, StatusBannerTitle } from "@/components/ui/uai/status-banner";

const steps = ["contact", "delivery", "payment", "review"] as const;
const labels = { contact: "Contact", delivery: "Delivery", payment: "Payment", review: "Review" };

function PaymentPlaceholder({ label, value }: { label: string; value: string }) {
  return (
    <div style={{ display: "grid", gap: 6 }}>
      <span style={{ fontSize: 12.5, fontWeight: 500 }}>{label}</span>
      <span
        style={{
          padding: "8px 12px",
          borderRadius: 8,
          background: "var(--uai-surface)",
          boxShadow: "inset 0 0 0 1px var(--uai-border)",
          color: "var(--uai-subtle)",
          fontVariantNumeric: "tabular-nums",
        }}
      >
        {value}
      </span>
    </div>
  );
}

export function CheckoutPreview({ variant = "split" }: { variant?: CheckoutVariant }) {
  const [entries, setEntries] = useState<Record<string, string>>({});
  const [attempts, setAttempts] = useState(0);
  const field = (name: string, label: string, type = "text", autoComplete?: string) => (
    <FormField required variant={variant === "compact" ? "compact" : "outlined"}>
      <FormFieldLabel>{label}</FormFieldLabel>
      <FormFieldInput name={name} type={type} autoComplete={autoComplete} />
    </FormField>
  );
  return (
    <Checkout
      variant={variant}
      steps={steps}
      onStepComplete={(_step, data) =>
        setEntries((current) => ({
          ...current,
          ...Object.fromEntries([...data.entries()].map(([key, value]) => [key, String(value)])),
        }))
      }
      onPlaceOrder={async () => {
        await new Promise((resolve) => setTimeout(resolve, 800));
        setAttempts((count) => count + 1);
        if (attempts === 0) throw new Error("declined");
      }}
    >
      <CheckoutHeader>
        <CheckoutTitle>Checkout</CheckoutTitle>
        <CheckoutDescription>Fieldhouse Ceramics · 3 items</CheckoutDescription>
      </CheckoutHeader>
      <CheckoutProgress>
        {steps.map((step, index) => (
          <CheckoutProgressStep key={step} value={step}>
            {index + 1}. {labels[step]}
          </CheckoutProgressStep>
        ))}
      </CheckoutProgress>
      <CheckoutMain>
        <CheckoutSection value="contact">
          <CheckoutSectionHeader>
            <CheckoutSectionTitle>Contact</CheckoutSectionTitle>
            <CheckoutSectionEdit />
          </CheckoutSectionHeader>
          <CheckoutSectionSummary>{entries.email}</CheckoutSectionSummary>
          <CheckoutSectionForm>
            {field("email", "Email", "email", "email")}
            <CheckoutSectionContinue>Continue to delivery</CheckoutSectionContinue>
          </CheckoutSectionForm>
        </CheckoutSection>
        <CheckoutSection value="delivery">
          <CheckoutSectionHeader>
            <CheckoutSectionTitle>Delivery</CheckoutSectionTitle>
            <CheckoutSectionEdit />
          </CheckoutSectionHeader>
          <CheckoutSectionSummary>
            {entries.name}, {entries.address}, {entries.postal}
          </CheckoutSectionSummary>
          <CheckoutSectionForm>
            {field("name", "Full name", "text", "name")}
            {field("address", "Street address", "text", "street-address")}
            <CheckoutFieldRow>
              {field("city", "City", "text", "address-level2")}
              {field("postal", "Postal code", "text", "postal-code")}
            </CheckoutFieldRow>
            <CheckoutSectionContinue>Continue to payment</CheckoutSectionContinue>
          </CheckoutSectionForm>
        </CheckoutSection>
        <CheckoutSection value="payment">
          <CheckoutSectionHeader>
            <CheckoutSectionTitle>Payment</CheckoutSectionTitle>
            <CheckoutSectionEdit />
          </CheckoutSectionHeader>
          <CheckoutSectionSummary>Visa ending in 4242 · expires 08/29</CheckoutSectionSummary>
          <CheckoutSectionForm>
            <CheckoutPayment>
              <PaymentPlaceholder label="Card number" value="Hosted by your payment provider" />
              <CheckoutFieldRow>
                <PaymentPlaceholder label="Expiry" value="MM / YY" />
                <PaymentPlaceholder label="Security code" value="•••" />
              </CheckoutFieldRow>
              <CheckoutPaymentNote>
                Placeholder region. Card fields come from your payment provider; this preview
                collects nothing.
              </CheckoutPaymentNote>
            </CheckoutPayment>
            <CheckoutSectionContinue>Review order</CheckoutSectionContinue>
          </CheckoutSectionForm>
        </CheckoutSection>
        <CheckoutSection value="review">
          <CheckoutSectionHeader>
            <CheckoutSectionTitle>Review</CheckoutSectionTitle>
          </CheckoutSectionHeader>
          <CheckoutSectionForm>
            <p style={{ margin: 0, color: "var(--uai-muted)" }}>
              Check your details, then place the order. The first attempt in this preview is
              declined so you can see the error state.
            </p>
            <CheckoutError>
              <StatusBannerTitle>Your card was declined</StatusBannerTitle>
              <StatusBannerDescription>
                Nothing was charged. Try again or use another payment method.
              </StatusBannerDescription>
            </CheckoutError>
            <CheckoutPlaceOrder>Place order · $142.00</CheckoutPlaceOrder>
          </CheckoutSectionForm>
        </CheckoutSection>
      </CheckoutMain>
      <CheckoutConfirmation>
        <CheckoutConfirmationTitle>Order FH-20418 is confirmed</CheckoutConfirmationTitle>
        <p style={{ margin: 0 }}>
          A receipt is on its way to {entries.email || "your inbox"}. We will email tracking as soon
          as your order ships.
        </p>
      </CheckoutConfirmation>
      <CheckoutSummary>
        <CheckoutSummaryTotals>
          <PriceSummaryHeader>
            <PriceSummaryTitle>Order summary</PriceSummaryTitle>
          </PriceSummaryHeader>
          <PriceSummaryList>
            <PriceSummaryItem label="Pour-over set × 1">$68.00</PriceSummaryItem>
            <PriceSummaryItem label="Low tumbler × 2">$68.00</PriceSummaryItem>
            <PriceSummaryItem label="Shipping" tone="success">
              Free
            </PriceSummaryItem>
            <PriceSummaryItem label="Tax" tone="muted">
              $6.00
            </PriceSummaryItem>
            <PriceSummaryTotal>$142.00</PriceSummaryTotal>
          </PriceSummaryList>
        </CheckoutSummaryTotals>
      </CheckoutSummary>
    </Checkout>
  );
}
```

## Anatomy

- `FormField`
  - `FormFieldLabel`
  - `FormFieldInput`
- `Checkout`
  - `CheckoutHeader`
    - `CheckoutTitle`
    - `CheckoutDescription`
  - `CheckoutProgress`
    - `CheckoutProgressStep`
  - `CheckoutMain`
    - `CheckoutSection`
      - `CheckoutSectionHeader`
        - `CheckoutSectionTitle`
        - `CheckoutSectionEdit`
      - `CheckoutSectionSummary`
      - `CheckoutSectionForm`
        - `CheckoutSectionContinue`
        - `CheckoutFieldRow`
        - `CheckoutPayment`
          - `CheckoutPaymentNote`
        - `CheckoutError`
          - `StatusBannerTitle`
          - `StatusBannerDescription`
        - `CheckoutPlaceOrder`
  - `CheckoutConfirmation`
    - `CheckoutConfirmationTitle`
  - `CheckoutSummary`
    - `CheckoutSummaryTotals`
      - `PriceSummaryHeader`
        - `PriceSummaryTitle`
      - `PriceSummaryList`
        - `PriceSummaryItem`
        - `PriceSummaryTotal`

## Accessibility

- Each step is a labelled section with a native form, so required fields validate before the step completes.
- Continue and Edit move focus to the heading of the step that opens; completed headings include visually hidden ", complete" text.
- Step Indicator mirrors progress with aria-current="step" on the current step.
- The payment slot is a labelled fieldset for provider-hosted fields; the block never renders card inputs.
- A rejected order shows an assertive Status Banner; a placed order replaces the steps with a status region whose heading receives focus.
