Skip to content
UI

Search docs

Search components and documentation

New
← Back to ecommerce blocks

checkout-01

A two-column checkout: contact, shipping address and payment form fields on one side, an order summary (line items, subtotal, shipping, tax, total) with a Pay button on the other. Use as the checkout page of a storefront.

Code

import * as React from "react"
import { LockIcon } from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"

const summary = [
  { name: "Aero Runner", variant: "Size 42 · Slate", price: 129 },
  { name: "Trail Cap", variant: "One size · Black", price: 34 },
]

const money = (n: number) =>
  new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(n)

export function Checkout({ className, ...props }: React.ComponentProps<"div">) {
  const subtotal = summary.reduce((s, i) => s + i.price, 0)
  const shipping = 0
  const tax = Math.round(subtotal * 0.08 * 100) / 100
  const total = subtotal + shipping + tax

  return (
    <div
      className={cn(
        "mx-auto grid w-full max-w-5xl gap-10 px-6 py-12 lg:grid-cols-[1fr_22rem]",
        className
      )}
      {...props}
    >
      <form className="flex flex-col gap-8">
        <section className="flex flex-col gap-4">
          <h2 className="font-heading text-lg font-semibold tracking-tight">
            Contact
          </h2>
          <div className="grid gap-2">
            <Label htmlFor="checkout-email">Email</Label>
            <Input id="checkout-email" type="email" placeholder="you@example.com" />
          </div>
        </section>

        <section className="flex flex-col gap-4">
          <h2 className="font-heading text-lg font-semibold tracking-tight">
            Shipping address
          </h2>
          <div className="grid gap-4 sm:grid-cols-2">
            <div className="grid gap-2">
              <Label htmlFor="checkout-first">First name</Label>
              <Input id="checkout-first" autoComplete="given-name" />
            </div>
            <div className="grid gap-2">
              <Label htmlFor="checkout-last">Last name</Label>
              <Input id="checkout-last" autoComplete="family-name" />
            </div>
          </div>
          <div className="grid gap-2">
            <Label htmlFor="checkout-address">Address</Label>
            <Input id="checkout-address" autoComplete="street-address" />
          </div>
          <div className="grid gap-4 sm:grid-cols-3">
            <div className="grid gap-2 sm:col-span-2">
              <Label htmlFor="checkout-city">City</Label>
              <Input id="checkout-city" autoComplete="address-level2" />
            </div>
            <div className="grid gap-2">
              <Label htmlFor="checkout-zip">Postal code</Label>
              <Input id="checkout-zip" autoComplete="postal-code" />
            </div>
          </div>
        </section>

        <section className="flex flex-col gap-4">
          <h2 className="font-heading text-lg font-semibold tracking-tight">
            Payment
          </h2>
          <div className="grid gap-2">
            <Label htmlFor="checkout-card">Card number</Label>
            <Input id="checkout-card" inputMode="numeric" placeholder="1234 1234 1234 1234" />
          </div>
          <div className="grid gap-4 sm:grid-cols-2">
            <div className="grid gap-2">
              <Label htmlFor="checkout-exp">Expiry</Label>
              <Input id="checkout-exp" placeholder="MM/YY" />
            </div>
            <div className="grid gap-2">
              <Label htmlFor="checkout-cvc">CVC</Label>
              <Input id="checkout-cvc" inputMode="numeric" placeholder="123" />
            </div>
          </div>
        </section>
      </form>

      <Card className="h-fit">
        <CardContent className="flex flex-col gap-4 pt-6">
          <ul className="flex flex-col gap-3">
            {summary.map((item) => (
              <li key={item.name} className="flex items-start justify-between gap-3">
                <div className="min-w-0">
                  <p className="truncate text-sm font-medium">{item.name}</p>
                  <p className="text-muted-foreground truncate text-xs">
                    {item.variant}
                  </p>
                </div>
                <span className="text-sm tabular-nums">{money(item.price)}</span>
              </li>
            ))}
          </ul>
          <Separator />
          <dl className="flex flex-col gap-2 text-sm">
            <div className="flex justify-between">
              <dt className="text-muted-foreground">Subtotal</dt>
              <dd className="tabular-nums">{money(subtotal)}</dd>
            </div>
            <div className="flex justify-between">
              <dt className="text-muted-foreground">Shipping</dt>
              <dd className="tabular-nums">Free</dd>
            </div>
            <div className="flex justify-between">
              <dt className="text-muted-foreground">Tax</dt>
              <dd className="tabular-nums">{money(tax)}</dd>
            </div>
          </dl>
          <Separator />
          <div className="flex justify-between text-base font-semibold">
            <span>Total</span>
            <span className="tabular-nums">{money(total)}</span>
          </div>
          <Button className="w-full" size="lg">
            <LockIcon className="size-4" />
            Pay {money(total)}
          </Button>
          <p className="text-muted-foreground text-center text-xs">
            Secure checkout — your details are encrypted.
          </p>
        </CardContent>
      </Card>
    </div>
  )
}