---
title: Checkout
description: Checkout layouts for carts, payment, and shipping.
group: Blocks
order: 17
---

The core pieces of a checkout flow, laid out and ready to connect to your commerce stack.

## Payment

A split view with the order summary beside the card form.

```tsx
"use client";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";

type Checkout01Props = {
  className?: string;
};

const ITEMS = [
  {
    name: "Arctis Pro seat",
    detail: "Annual billing",
    price: "$348",
    image:
      "https://images.unsplash.com/photo-1523275335684-37898b6baf30?auto=format&fit=crop&w=160&q=80",
  },
  {
    name: "Blocks pack",
    detail: "Marketing kit",
    price: "$49",
    image:
      "https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=160&q=80",
  },
] as const;

export function Checkout01({ className }: Checkout01Props) {
  return (
    <section className={cn("@container w-full bg-background", className)}>
      <div
        data-slot="block-split"
        className="mx-auto grid w-full max-w-4xl gap-10 px-4 py-10 @[40rem]:grid-cols-2 @[40rem]:gap-12 @[40rem]:px-6 @[40rem]:py-14"
      >
        <div>
          <h2 className="text-xl font-medium tracking-wide text-foreground @[32rem]:text-2xl">
            Order summary
          </h2>
          <p className="mt-2 text-sm tracking-wide text-muted-foreground">
            Review your cart before paying.
          </p>

          <ul className="mt-8 flex flex-col gap-4">
            {ITEMS.map((item) => (
              <li key={item.name} className="flex items-center gap-3">
                <img
                  src={item.image}
                  alt=""
                  className="size-14 shrink-0 rounded-lg object-cover"
                />
                <div className="min-w-0 flex-1">
                  <p className="truncate text-sm font-medium tracking-wide text-foreground">
                    {item.name}
                  </p>
                  <p className="text-xs tracking-wide text-muted-foreground">
                    {item.detail}
                  </p>
                </div>
                <p className="shrink-0 text-sm tracking-wide text-foreground tabular-nums">
                  {item.price}
                </p>
              </li>
            ))}
          </ul>

          <div className="mt-8 flex flex-col gap-2 border-t border-border pt-4 text-sm tracking-wide">
            <div className="flex justify-between text-muted-foreground">
              <span>Subtotal</span>
              <span className="tabular-nums">$397</span>
            </div>
            <div className="flex justify-between text-muted-foreground">
              <span>Tax</span>
              <span className="tabular-nums">$31.76</span>
            </div>
            <div className="flex justify-between font-medium text-foreground">
              <span>Total</span>
              <span className="tabular-nums">$428.76</span>
            </div>
          </div>
        </div>

        <form
          className="flex flex-col gap-4"
          onSubmit={(event) => event.preventDefault()}
        >
          <h3 className="text-lg font-medium tracking-wide text-foreground">
            Payment
          </h3>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="checkout-01-email">Email</Label>
            <Input
              id="checkout-01-email"
              name="email"
              type="email"
              placeholder="maya@northline.com"
              className="border-0 bg-muted"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="checkout-01-card">Card number</Label>
            <Input
              id="checkout-01-card"
              name="card"
              inputMode="numeric"
              placeholder="ACCT-000015"
              className="border-0 bg-muted"
            />
          </div>
          <div className="grid grid-cols-2 gap-4">
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="checkout-01-expiry">Expiry</Label>
              <Input
                id="checkout-01-expiry"
                name="expiry"
                placeholder="MM / YY"
                className="border-0 bg-muted"
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="checkout-01-cvc">CVC</Label>
              <Input
                id="checkout-01-cvc"
                name="cvc"
                placeholder="123"
                className="border-0 bg-muted"
              />
            </div>
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="checkout-01-name">Name on card</Label>
            <Input
              id="checkout-01-name"
              name="name"
              placeholder="Maya Chen"
              className="border-0 bg-muted"
            />
          </div>
          <Button type="submit" size="sm" className="mt-2 w-full">
            Pay $428.76
          </Button>
          <p className="text-center text-xs tracking-wide text-muted-foreground">
            Secured checkout. You won’t be charged in this demo.
          </p>
        </form>
      </div>
    </section>
  );
}
```

### Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add checkout-01
```

### Usage

```tsx
import { Checkout01 } from "@/components/blocks/checkout/checkout-01"
```

```tsx
<Checkout01 />
```

## Cart

An interactive cart with quantity controls, a promo field, and checkout action.

```tsx
"use client";

import { useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";

type Checkout02Props = {
  className?: string;
};

type CartItem = {
  id: string;
  name: string;
  detail: string;
  price: number;
  qty: number;
  image: string;
};

const INITIAL: CartItem[] = [
  {
    id: "seat",
    name: "Studio license",
    detail: "1 seat",
    price: 79,
    qty: 1,
    image:
      "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?auto=format&fit=crop&w=160&q=80",
  },
  {
    id: "theme",
    name: "Theme add-on",
    detail: "Dark kit",
    price: 24,
    qty: 2,
    image:
      "https://images.unsplash.com/photo-1572635196237-14b3f281503f?auto=format&fit=crop&w=160&q=80",
  },
];

function formatMoney(value: number) {
  return `$${value.toFixed(2)}`;
}

export function Checkout02({ className }: Checkout02Props) {
  const [items, setItems] = useState(INITIAL);
  const [promo, setPromo] = useState("");
  const [applied, setApplied] = useState(false);

  const subtotal = useMemo(
    () => items.reduce((sum, item) => sum + item.price * item.qty, 0),
    [items],
  );
  const discount = applied ? 15 : 0;
  const total = Math.max(0, subtotal - discount);

  function setQty(id: string, next: number) {
    setItems((current) =>
      current.map((item) =>
        item.id === id ? { ...item, qty: Math.max(1, next) } : item,
      ),
    );
  }

  return (
    <section className={cn("@container w-full bg-background", className)}>
      <div className="mx-auto w-full max-w-xl px-4 py-10 @[32rem]:px-6 @[32rem]:py-14">
        <div>
          <h2 className="text-xl font-medium tracking-wide text-foreground @[32rem]:text-2xl">
            Your cart
          </h2>
          <p className="mt-2 text-sm tracking-wide text-muted-foreground">
            Adjust quantities, then continue to payment.
          </p>
        </div>

        <ul className="mt-8 flex flex-col gap-4">
          {items.map((item) => (
            <li
              key={item.id}
              className="flex items-center gap-3 rounded-xl bg-muted px-3 py-3"
            >
              <img
                src={item.image}
                alt=""
                className="size-14 shrink-0 rounded-lg object-cover"
              />
              <div className="min-w-0 flex-1">
                <p className="truncate text-sm font-medium tracking-wide text-foreground">
                  {item.name}
                </p>
                <p className="text-xs tracking-wide text-muted-foreground">
                  {item.detail} · {formatMoney(item.price)}
                </p>
              </div>
              <div className="flex items-center gap-1">
                <button
                  type="button"
                  aria-label={`Decrease ${item.name}`}
                  className="inline-flex size-7 shrink-0 items-center justify-center rounded-sm text-sm text-foreground/50 transition-colors duration-200 ease-out hover:bg-surface-hover hover:text-foreground"
                  onClick={() => setQty(item.id, item.qty - 1)}
                >
                  −
                </button>
                <span className="w-4 text-center text-sm tracking-wide text-foreground tabular-nums">
                  {item.qty}
                </span>
                <button
                  type="button"
                  aria-label={`Increase ${item.name}`}
                  className="inline-flex size-7 shrink-0 items-center justify-center rounded-sm text-sm text-foreground/50 transition-colors duration-200 ease-out hover:bg-surface-hover hover:text-foreground"
                  onClick={() => setQty(item.id, item.qty + 1)}
                >
                  +
                </button>
              </div>
            </li>
          ))}
        </ul>

        {items.length === 0 ? (
          <p className="mt-8 text-sm tracking-wide text-muted-foreground">
            Your cart is empty.
          </p>
        ) : (
          <>
            <form
              className="mt-6 flex gap-2"
              onSubmit={(event) => {
                event.preventDefault();
                setApplied(promo.trim().toLowerCase() === "arctis");
              }}
            >
              <Input
                name="promo"
                value={promo}
                onChange={(event) => {
                  setPromo(event.target.value);
                  setApplied(false);
                }}
                placeholder="Promo code"
                className="border-0 bg-muted"
              />
              <Button type="submit" size="sm" variant="secondary" className="h-9">
                Apply
              </Button>
            </form>
            {applied ? (
              <p className="mt-2 text-xs tracking-wide text-muted-foreground">
                Code ARCTIS applied (−$15.00)
              </p>
            ) : null}

            <div className="mt-6 flex flex-col gap-2 text-sm tracking-wide">
              <div className="flex justify-between text-muted-foreground">
                <span>Subtotal</span>
                <span className="tabular-nums">{formatMoney(subtotal)}</span>
              </div>
              {applied ? (
                <div className="flex justify-between text-muted-foreground">
                  <span>Discount</span>
                  <span className="tabular-nums">−{formatMoney(discount)}</span>
                </div>
              ) : null}
              <div className="flex justify-between font-medium text-foreground">
                <span>Total</span>
                <span className="tabular-nums">{formatMoney(total)}</span>
              </div>
            </div>

            <Button type="button" size="sm" className="mt-6 w-full">
              Checkout · {formatMoney(total)}
            </Button>
          </>
        )}
      </div>
    </section>
  );
}
```

### Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add checkout-02
```

### Usage

```tsx
import { Checkout02 } from "@/components/blocks/checkout/checkout-02"
```

```tsx
<Checkout02 />
```

## Shipping

An address form, shipping options, and an order total that updates alongside them.

```tsx
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { cn } from "@/lib/utils";

type Checkout03Props = {
  className?: string;
};

const SHIPPING = [
  {
    value: "standard",
    label: "Standard",
    detail: "4–6 business days",
    price: 0,
  },
  {
    value: "express",
    label: "Express",
    detail: "1–2 business days",
    price: 18,
  },
] as const;

export function Checkout03({ className }: Checkout03Props) {
  const [shipping, setShipping] = useState("standard");
  const shippingCost =
    SHIPPING.find((option) => option.value === shipping)?.price ?? 0;
  const subtotal = 129;
  const total = subtotal + shippingCost;

  return (
    <section className={cn("@container w-full bg-background", className)}>
      <div
        data-slot="block-split"
        className="mx-auto grid w-full max-w-4xl gap-10 px-4 py-10 @[40rem]:grid-cols-[1.2fr_0.8fr] @[40rem]:items-start @[40rem]:gap-12 @[40rem]:px-6 @[40rem]:py-14"
      >
        <form
          className="flex flex-col gap-8"
          onSubmit={(event) => event.preventDefault()}
        >
          <div>
            <h2 className="text-xl font-medium tracking-wide text-foreground @[32rem]:text-2xl">
              Shipping
            </h2>
            <p className="mt-2 text-sm tracking-wide text-muted-foreground">
              Where should we send your order?
            </p>
          </div>

          <div className="grid grid-cols-1 gap-4 @[32rem]:grid-cols-2">
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="checkout-03-first">First name</Label>
              <Input
                id="checkout-03-first"
                name="firstName"
                placeholder="Jordan"
                className="border-0 bg-muted"
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="checkout-03-last">Last name</Label>
              <Input
                id="checkout-03-last"
                name="lastName"
                placeholder="Hale"
                className="border-0 bg-muted"
              />
            </div>
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="checkout-03-address">Address</Label>
            <Input
              id="checkout-03-address"
              name="address"
              placeholder="184 Market Street"
              className="border-0 bg-muted"
            />
          </div>
          <div className="grid grid-cols-1 gap-4 @[32rem]:grid-cols-3">
            <div className="flex flex-col gap-1.5 @[32rem]:col-span-1">
              <Label htmlFor="checkout-03-city">City</Label>
              <Input
                id="checkout-03-city"
                name="city"
                placeholder="Austin"
                className="border-0 bg-muted"
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="checkout-03-state">State</Label>
              <Input
                id="checkout-03-state"
                name="state"
                placeholder="TX"
                className="border-0 bg-muted"
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="checkout-03-zip">ZIP</Label>
              <Input
                id="checkout-03-zip"
                name="zip"
                placeholder="78701"
                className="border-0 bg-muted"
              />
            </div>
          </div>

          <div>
            <p className="text-sm font-medium tracking-wide text-foreground">
              Shipping method
            </p>
            <RadioGroup
              value={shipping}
              onValueChange={setShipping}
              className="mt-3 flex flex-col gap-2"
            >
              {SHIPPING.map((option) => (
                <div
                  key={option.value}
                  className="flex items-center gap-3 rounded-xl bg-muted px-3 py-3"
                >
                  <RadioGroupItem
                    id={`checkout-03-${option.value}`}
                    value={option.value}
                  />
                  <label
                    htmlFor={`checkout-03-${option.value}`}
                    className="min-w-0 flex-1 cursor-pointer"
                  >
                    <span className="block text-sm font-medium tracking-wide text-foreground">
                      {option.label}
                    </span>
                    <span className="block text-xs tracking-wide text-muted-foreground">
                      {option.detail}
                    </span>
                  </label>
                  <span className="shrink-0 text-sm tracking-wide text-foreground tabular-nums">
                    {option.price === 0 ? "Free" : `$${option.price}`}
                  </span>
                </div>
              ))}
            </RadioGroup>
          </div>

          <Button type="submit" size="sm" className="w-full @[40rem]:w-auto">
            Continue to payment
          </Button>
        </form>

        <aside className="rounded-xl bg-muted p-5">
          <h3 className="text-sm font-medium tracking-wide text-foreground">
            Order summary
          </h3>
          <ul className="mt-4 flex flex-col gap-3 text-sm tracking-wide">
            <li className="flex justify-between gap-3">
              <span className="text-muted-foreground">Canvas tote</span>
              <span className="tabular-nums text-foreground">$89.00</span>
            </li>
            <li className="flex justify-between gap-3">
              <span className="text-muted-foreground">Marking kit</span>
              <span className="tabular-nums text-foreground">$40.00</span>
            </li>
          </ul>
          <div className="mt-4 flex flex-col gap-2 border-t border-border pt-4 text-sm tracking-wide">
            <div className="flex justify-between text-muted-foreground">
              <span>Subtotal</span>
              <span className="tabular-nums">${subtotal.toFixed(2)}</span>
            </div>
            <div className="flex justify-between text-muted-foreground">
              <span>Shipping</span>
              <span className="tabular-nums">
                {shippingCost === 0 ? "Free" : `$${shippingCost.toFixed(2)}`}
              </span>
            </div>
            <div className="flex justify-between font-medium text-foreground">
              <span>Total</span>
              <span className="tabular-nums">${total.toFixed(2)}</span>
            </div>
          </div>
        </aside>
      </div>
    </section>
  );
}
```

### Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add checkout-03
```

### Usage

```tsx
import { Checkout03 } from "@/components/blocks/checkout/checkout-03"
```

```tsx
<Checkout03 />
```
