Menu

Change language

GitHub repo

Order Confirmation

blockcommerce

Post-purchase order confirmation & tracking page featuring a success banner with emerald checkmark halo, 4-step shipment progress bar with pulsing active state, itemized order breakdown, and customer delivery & payment cards.

Installation

$npx shadcn@latest add https://uipkge.dev/r/lit/order-confirmation.json
Manifest:https://uipkge.dev/r/lit/order-confirmation.jsonInstalls to:src/uip/blocks/order-confirmation/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

OrderItem
interface OrderItem {
  id: string
  name: string
  variant: string
  quantity: number
  price: string
  image: string
}
TrackingStep
interface TrackingStep {
  id: string
  title: string
  date: string
  status: 'completed' | 'current' | 'upcoming'
  description: string
}
HTMLElementTagNameMap
interface HTMLElementTagNameMap {
    'uip-order-confirmation': UipOrderConfirmation
  }
}

Files installed (1)

  • src/uip/blocks/order-confirmation/order-confirmation.ts18.7 kB
    import { LitElement, css, html } from 'lit'
    import {
      Check,
      CheckCircle2,
      CreditCard,
      Download,
      ExternalLink,
      HelpCircle,
      Mail,
      MapPin,
      MessageSquare,
      Package,
      Truck,
    } from 'lucide'
    import { cn } from '../../lib/utils'
    import { tailwind } from '../../lib/styles'
    import { ThemeController } from '../../lib/theme'
    import { icon } from '../../lib/icon'
    // Side-effect imports: register the composed elements for standalone use
    // (consumers who install only this block still get working primitives).
    import '../../components/badge/badge'
    import '../../components/button/button'
    import '../../components/card/card'
    import '../../components/separator/separator'
    
    interface OrderItem {
      id: string
      name: string
      variant: string
      quantity: number
      price: string
      image: string
    }
    
    interface TrackingStep {
      id: string
      title: string
      date: string
      status: 'completed' | 'current' | 'upcoming'
      description: string
    }
    
    const orderItems: OrderItem[] = [
      {
        id: 'item-1',
        name: 'Studio Wireless Noise-Canceling Headphones',
        variant: 'Space Gray • Over-Ear',
        quantity: 1,
        price: '$179.00',
        image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=160&auto=format&fit=crop&q=80',
      },
      {
        id: 'item-2',
        name: 'Braided USB-C Fast Charging Cable (2m)',
        variant: 'Midnight Black • 100W PD',
        quantity: 2,
        price: '$38.00',
        image: 'https://images.unsplash.com/photo-1546868871-7041f2a55e12?w=160&auto=format&fit=crop&q=80',
      },
      {
        id: 'item-3',
        name: 'Magnetic Aluminum Desk Stand',
        variant: 'Silver • Anodized Aluminum',
        quantity: 1,
        price: '$35.00',
        image: 'https://images.unsplash.com/photo-1586105251261-72a756497a11?w=160&auto=format&fit=crop&q=80',
      },
    ]
    
    const trackingSteps: TrackingStep[] = [
      {
        id: 'step-1',
        title: 'Order Placed',
        date: 'Aug 21, 2026 • 10:42 AM',
        status: 'completed',
        description: 'Payment authorized & order confirmed',
      },
      {
        id: 'step-2',
        title: 'Processing',
        date: 'Aug 22, 2026 • 02:15 PM',
        status: 'completed',
        description: 'Item picked & packed at warehouse',
      },
      {
        id: 'step-3',
        title: 'Shipped',
        date: 'Aug 23, 2026 • 09:30 AM',
        status: 'current',
        description: 'In transit via FedEx Priority • Chicago, IL',
      },
      {
        id: 'step-4',
        title: 'Delivered',
        date: 'Est. Aug 27, 2026',
        status: 'upcoming',
        description: 'Estimated delivery to front door',
      },
    ]
    
    /**
     * <uip-order-confirmation> — the registry OrderConfirmation block as a web component.
     *
     * Class strings and sample data are React's verbatim. The block is fully
     * static (React holds no state): success banner, desktop + mobile tracking
     * stepper, itemized summary with cost breakdown, and shipping / payment /
     * help cards. No props, no events.
     */
    export class UipOrderConfirmation extends LitElement {
      static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
      // :host display can't be a utility class (the host has no template of its own).
      static styles = [tailwind, css`:host { display: block; }`]
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'order-confirmation')
      }
    
      render() {
        return html`
          <div data-slot="order-confirmation" class="w-full space-y-6">
            <uip-card class="border shadow-xs">
              <uip-card-content class="flex flex-col items-center p-6 text-center sm:p-10">
                <div
                  class="bg-success/10 text-success ring-success/10 text-success relative flex size-16 items-center justify-center rounded-full ring-8 sm:size-20"
                >
                  ${icon(CheckCircle2, 'circle-check', 'size-8 sm:size-10')}
                </div>
                <div class="mt-6 max-w-xl space-y-2">
                  <div
                    class="border-success/20 bg-success/10 text-success inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium"
                  >
                    <span class="bg-success size-1.5 rounded-full"></span>
                    Order Confirmed
                  </div>
                  <h1 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl">Thank you for your order!</h1>
                  <p class="text-muted-foreground text-sm sm:text-base">
                    We&apos;ve received your order <span class="text-foreground font-mono font-semibold">#ORD-92841</span> and
                    sent a confirmation email to <span class="text-foreground font-medium">[email protected]</span>.
                  </p>
                </div>
                <div class="mt-8 flex flex-wrap items-center justify-center gap-3">
                  <uip-button class="gap-2 shadow-xs">
                    ${icon(ExternalLink, 'external-link', 'size-4')} Track Shipment
                  </uip-button>
                  <uip-button aria-label="Download attachment" variant="outline" class="gap-2 shadow-xs">
                    ${icon(Download, 'download', 'size-4')} Download Receipt
                  </uip-button>
                  <uip-button variant="ghost" class="text-muted-foreground hover:text-foreground">Continue Shopping</uip-button>
                </div>
              </uip-card-content>
            </uip-card>
    
            <uip-card class="border shadow-xs">
              <uip-card-header class="flex flex-col gap-3 pb-4 sm:flex-row sm:items-center sm:justify-between">
                <div class="space-y-1">
                  <div class="flex items-center gap-2">
                    ${icon(Truck, 'truck', 'text-primary size-4')}
                    <uip-card-title class="text-base font-semibold">Shipment Progress</uip-card-title>
                    <uip-badge variant="secondary" class="bg-primary/10 text-primary text-xs font-medium">In Transit</uip-badge>
                  </div>
                  <p class="text-muted-foreground text-xs">
                    Carrier: <span class="text-foreground font-medium">FedEx Priority</span> • Tracking:
                    <span class="text-foreground font-mono font-medium">#FX-9821734910</span>
                  </p>
                </div>
                <div class="border-border/80 bg-muted/40 rounded-lg border px-3 py-1.5 text-xs">
                  <span class="text-muted-foreground">Estimated Delivery:</span>
                  <span class="text-foreground ml-1 font-semibold">Thursday, Aug 27, 2026</span>
                </div>
              </uip-card-header>
              <uip-card-content class="pt-4">
                <div class="hidden md:block">
                  <div class="grid grid-cols-4 gap-4">
                    ${trackingSteps.map(
                      (step, idx) => html`<div class="relative flex flex-col">
                        <div class="relative flex items-center">
                          ${idx > 0
                            ? html`<div
                                class=${cn(
                                  'absolute top-1/2 right-1/2 -z-0 h-0.5 w-full -translate-y-1/2',
                                  step.status === 'upcoming' ? 'bg-muted' : 'bg-primary',
                                )}
                              ></div>`
                            : html``}
                          ${idx < trackingSteps.length - 1
                            ? html`<div
                                class=${cn(
                                  'absolute top-1/2 left-1/2 -z-0 h-0.5 w-full -translate-y-1/2',
                                  trackingSteps[idx + 1]?.status === 'upcoming' ? 'bg-muted' : 'bg-primary',
                                )}
                              ></div>`
                            : html``}
                          <div class="relative z-10 mx-auto flex items-center justify-center">
                            ${step.status === 'completed'
                              ? html`<div
                                  class="bg-primary text-primary-foreground ring-background flex size-8 items-center justify-center rounded-full ring-4"
                                >
                                  ${icon(Check, 'check', 'size-4')}
                                </div>`
                              : step.status === 'current'
                                ? html`<div
                                    class="border-primary bg-background text-primary ring-background flex size-8 items-center justify-center rounded-full border-2 ring-4"
                                  >
                                    <span class="relative flex size-2.5">
                                      <span class="bg-primary absolute inline-flex h-full w-full rounded-full opacity-75"></span>
                                      <span class="bg-primary relative inline-flex size-2.5 rounded-full"></span>
                                    </span>
                                  </div>`
                                : html`<div
                                    class="border-muted-foreground/30 bg-muted/40 text-muted-foreground ring-background flex size-8 items-center justify-center rounded-full border-2 ring-4"
                                  >
                                    ${icon(Package, 'package', 'size-3.5')}
                                  </div>`}
                          </div>
                        </div>
                        <div class="mt-3 text-center">
                          <p class=${cn('text-sm font-semibold', step.status === 'upcoming' ? 'text-muted-foreground' : 'text-foreground')}>
                            ${step.title}
                          </p>
                          <p class="text-muted-foreground mt-0.5 text-xs tabular-nums">${step.date}</p>
                          <p class="text-muted-foreground/80 mt-0.5 text-xs">${step.description}</p>
                        </div>
                      </div>`,
                    )}
                  </div>
                </div>
    
                <div class="space-y-4 md:hidden">
                  ${trackingSteps.map(
                    (step, idx) => html`<div class="flex items-start gap-3">
                      <div class="flex flex-col items-center">
                        ${step.status === 'completed'
                          ? html`<div class="bg-primary text-primary-foreground flex size-7 shrink-0 items-center justify-center rounded-full">
                              ${icon(Check, 'check', 'size-3.5')}
                            </div>`
                          : step.status === 'current'
                            ? html`<div
                                class="border-primary bg-background text-primary flex size-7 shrink-0 items-center justify-center rounded-full border-2"
                              >
                                <span class="relative flex size-2">
                                  <span class="bg-primary absolute inline-flex h-full w-full rounded-full opacity-75"></span>
                                  <span class="bg-primary relative inline-flex size-2 rounded-full"></span>
                                </span>
                              </div>`
                            : html`<div
                                class="border-muted-foreground/30 bg-muted/40 text-muted-foreground flex size-7 shrink-0 items-center justify-center rounded-full border-2"
                              >
                                ${icon(Package, 'package', 'size-3')}
                              </div>`}
                        ${idx < trackingSteps.length - 1
                          ? html`<div
                              class=${cn(
                                'mt-1 h-8 w-0.5',
                                trackingSteps[idx + 1]?.status === 'upcoming' ? 'bg-muted' : 'bg-primary',
                              )}
                            ></div>`
                          : html``}
                      </div>
                      <div class="min-w-0 flex-1 pt-0.5 pb-2">
                        <div class="flex items-center justify-between gap-2">
                          <p class=${cn('text-sm font-semibold', step.status === 'upcoming' ? 'text-muted-foreground' : 'text-foreground')}>
                            ${step.title}
                          </p>
                          <span class="text-muted-foreground text-xs tabular-nums">${step.date}</span>
                        </div>
                        <p class="text-muted-foreground mt-0.5 text-xs">${step.description}</p>
                      </div>
                    </div>`,
                  )}
                </div>
              </uip-card-content>
            </uip-card>
    
            <div class="grid grid-cols-1 gap-6 lg:grid-cols-12">
              <div class="lg:col-span-7">
                <uip-card class="border shadow-xs">
                  <uip-card-header class="pb-4">
                    <uip-card-title class="text-base font-semibold">Order Summary</uip-card-title>
                    <uip-card-description>3 items in shipment</uip-card-description>
                  </uip-card-header>
                  <uip-card-content class="space-y-6">
                    <div class="space-y-4">
                      ${orderItems.map(
                        (item) => html`<div class="flex items-center gap-4">
                          <div class="border-border bg-muted/40 relative size-16 shrink-0 overflow-hidden rounded-lg border">
                            <img src=${item.image} alt=${item.name} class="h-full w-full object-cover" />
                          </div>
                          <div class="min-w-0 flex-1">
                            <p class="text-foreground truncate text-sm font-medium">${item.name}</p>
                            <p class="text-muted-foreground text-xs">${item.variant}</p>
                            <p class="text-muted-foreground mt-1 text-xs">Qty: ${item.quantity}</p>
                          </div>
                          <div class="text-right">
                            <p class="text-foreground text-sm font-semibold tabular-nums">${item.price}</p>
                          </div>
                        </div>`,
                      )}
                    </div>
                    <uip-separator></uip-separator>
                    <div class="space-y-2 text-sm">
                      <div class="flex items-center justify-between">
                        <span class="text-muted-foreground">Subtotal</span>
                        <span class="text-foreground font-medium tabular-nums">$252.00</span>
                      </div>
                      <div class="flex items-center justify-between">
                        <span class="text-muted-foreground">Shipping (FedEx Priority)</span>
                        <span class="text-success text-success font-medium tabular-nums">Free</span>
                      </div>
                      <div class="flex items-center justify-between">
                        <span class="text-muted-foreground">Estimated Tax</span>
                        <span class="text-foreground font-medium tabular-nums">$12.50</span>
                      </div>
                    </div>
                    <uip-separator></uip-separator>
                    <div class="flex items-center justify-between">
                      <span class="text-foreground text-base font-semibold">Total</span>
                      <span class="text-foreground text-xl font-bold tabular-nums sm:text-2xl">$264.50</span>
                    </div>
                  </uip-card-content>
                </uip-card>
              </div>
    
              <div class="space-y-6 lg:col-span-5">
                <uip-card class="border shadow-xs">
                  <uip-card-header class="pb-3">
                    <div class="flex items-center gap-2">
                      ${icon(MapPin, 'map-pin', 'text-muted-foreground size-4')}
                      <uip-card-title class="text-base font-semibold">Shipping Address</uip-card-title>
                    </div>
                  </uip-card-header>
                  <uip-card-content class="space-y-3 text-sm">
                    <div class="space-y-0.5">
                      <p class="text-foreground font-medium">Alex Morgan</p>
                      <p class="text-muted-foreground">742 Evergreen Terrace, Suite 4B</p>
                      <p class="text-muted-foreground">Springfield, OR 97477</p>
                      <p class="text-muted-foreground">United States</p>
                      <p class="text-muted-foreground mt-1 text-xs">+1 (555) 234-5678</p>
                    </div>
                    <div class="border-border bg-muted/30 rounded-md border p-2.5 text-xs">
                      <span class="text-muted-foreground">Delivery Method:</span>
                      <span class="text-foreground ml-1 font-medium">FedEx Priority Express</span>
                    </div>
                  </uip-card-content>
                </uip-card>
    
                <uip-card class="border shadow-xs">
                  <uip-card-header class="pb-3">
                    <div class="flex items-center gap-2">
                      ${icon(CreditCard, 'credit-card', 'text-muted-foreground size-4')}
                      <uip-card-title class="text-base font-semibold">Payment Method</uip-card-title>
                    </div>
                  </uip-card-header>
                  <uip-card-content class="space-y-3 text-sm">
                    <div class="flex items-center justify-between">
                      <div class="flex items-center gap-2.5">
                        <div class="border-border bg-muted/40 text-foreground flex h-7 w-11 items-center justify-center rounded border font-mono text-xs font-bold">
                          VISA
                        </div>
                        <div>
                          <p class="text-foreground text-sm font-medium">Visa ending in 4242</p>
                          <p class="text-muted-foreground text-xs">Expires 08/29</p>
                        </div>
                      </div>
                      <uip-badge variant="secondary" class="bg-success/10 text-success">Paid</uip-badge>
                    </div>
                    <p class="text-muted-foreground text-xs">Billing address matches shipping address</p>
                  </uip-card-content>
                </uip-card>
    
                <uip-card class="border shadow-xs">
                  <uip-card-header class="pb-3">
                    <div class="flex items-center gap-2">
                      ${icon(HelpCircle, 'circle-help', 'text-muted-foreground size-4')}
                      <uip-card-title class="text-base font-semibold">Need Help?</uip-card-title>
                    </div>
                  </uip-card-header>
                  <uip-card-content class="space-y-3 text-sm">
                    <p class="text-muted-foreground text-xs">Questions about your order? Chat with support or email [email protected].</p>
                    <div class="space-y-2 pt-1">
                      <uip-button variant="outline" size="sm" class="w-full justify-start gap-2 text-xs shadow-xs">
                        ${icon(MessageSquare, 'message-square', 'size-3.5')} Chat with Support
                      </uip-button>
                      <uip-button variant="ghost" size="sm" class="text-muted-foreground hover:text-foreground w-full justify-start gap-2 text-xs">
                        ${icon(Mail, 'mail', 'size-3.5')} [email protected]
                      </uip-button>
                    </div>
                  </uip-card-content>
                </uip-card>
              </div>
            </div>
          </div>
        `
      }
    }
    
    customElements.get('uip-order-confirmation') || customElements.define('uip-order-confirmation', UipOrderConfirmation)
    
    declare global {
      interface HTMLElementTagNameMap {
        'uip-order-confirmation': UipOrderConfirmation
      }
    }
    

Raw manifest:https://uipkge.dev/r/lit/order-confirmation.json