Checkout

Checkout layouts for carts, payment, and shipping.

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.

Order summary

Review your cart before paying.

  • Arctis Pro seat

    Annual billing

    $348

  • Blocks pack

    Marketing kit

    $49

Subtotal$397
Tax$31.76
Total$428.76

Payment

Secured checkout. You won’t be charged in this demo.

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

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

Usage

import { Checkout01 } from "@/components/blocks/checkout/checkout-01"
<Checkout01 />

Cart

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

Your cart

Adjust quantities, then continue to payment.

  • Studio license

    1 seat · $79.00

    1
  • Theme add-on

    Dark kit · $24.00

    2
Subtotal$127.00
Total$127.00
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

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

Usage

import { Checkout02 } from "@/components/blocks/checkout/checkout-02"
<Checkout02 />

Shipping

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

Shipping

Where should we send your order?

Shipping method

Free
$18
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

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

Usage

import { Checkout03 } from "@/components/blocks/checkout/checkout-03"
<Checkout03 />