Checkout Modal

A multi-step checkout flow with plan review, billing details form, payment method input, and confirmation screen.

Preview

Features

  • 4-step checkout flow: Review → Details → Payment → Confirm
  • Visual step indicator with filled backgrounds and ring styling
  • Grouped form sections with icons (User, MapPin, CreditCard)
  • Mobile-friendly layout with wrapped steps, stacked actions, and scrollable dialog
  • Security badge with emerald color scheme
  • Success confirmation with order summary
  • Accessible with keyboard navigation

Install Summary

This kit will add the following files and dependencies to your project. Download the bundle and extract it into your project root.

Component Files

FilePath
checkout-modal.tsxcomponents/billing/checkout-modal/checkout-modal.tsx
checkout-context.tsxcomponents/billing/checkout-modal/checkout-context.tsx
checkout-steps.tsxcomponents/billing/checkout-modal/checkout-steps.tsx
step-review.tsxcomponents/billing/checkout-modal/step-review.tsx
step-details.tsxcomponents/billing/checkout-modal/step-details.tsx
step-payment.tsxcomponents/billing/checkout-modal/step-payment.tsx
step-confirm.tsxcomponents/billing/checkout-modal/step-confirm.tsx
types.tscomponents/billing/checkout-modal/types.ts
index.tscomponents/billing/checkout-modal/index.ts

Utility Files

FilePath
format.tslib/format.ts
design-tokens.tslib/design-tokens.ts

Dependencies

Install these dependencies before using the component:

Terminal
bash
npx shadcn@latest add dialog button input label
Terminal
bash
npm install @phosphor-icons/react

Installation

Download the complete bundle as a ZIP file, or copy the text bundle to your clipboard:

Component Files

The component consists of the following files:

1. checkout-modal.tsx

checkout-modal.tsx
tsx
"use client";

import {
  Dialog,
  DialogContent,

2. checkout-context.tsx

checkout-context.tsx
tsx
"use client";

import {
  createContext,
  useContext,

3. checkout-steps.tsx

checkout-steps.tsx
tsx
"use client";

import { Check } from "@phosphor-icons/react";
import { cn } from "@/lib/utils";
import type { CheckoutStep } from "./types";

4. step-review.tsx

step-review.tsx
tsx
"use client";

import { Receipt } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import { formatPrice } from "@/lib/format";

5. step-details.tsx

step-details.tsx
tsx
"use client";

import { useState } from "react";
import { User, MapPin } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";

6. step-payment.tsx

step-payment.tsx
tsx
"use client";

import { useState } from "react";
import { CreditCard, Lock, ShieldCheck } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";

7. step-confirm.tsx

step-confirm.tsx
tsx
"use client";

import { SealCheck as CheckCircle2 } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import { useCheckout } from "./checkout-context";

8. types.ts

types.ts
tsx
export type CheckoutStep = "review" | "details" | "payment" | "confirm";

export interface CheckoutPlan {
  id: string;
  name: string;

9. index.ts

index.ts
tsx
export { CheckoutModal } from "./checkout-modal";
export type {
  CheckoutStep,
  CheckoutPlan,
  BillingAddress,
  BillingDetails,
  CheckoutModalProps,
} from "./types";

Shared Utilities

This component uses shared utility functions. These are included in the bundle above:

format.ts

format.ts
tsx
export function formatDate(date: Date): string {
  return date.toLocaleDateString("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
  });
}

export function formatRelativeTime(date: Date, now: Date = new Date()): string {
  const diff = now.getTime() - date.getTime();
  const minutes = Math.floor(diff / 60000);

  if (minutes < 1) return "Just now";
  if (minutes < 60) return `${minutes}m ago`;

  const hours = Math.floor(minutes / 60);
  if (hours < 24) return `${hours}h ago`;

  const days = Math.floor(hours / 24);
  if (days < 7) return `${days}d ago`;

  return formatDate(date);
}

export function formatPrice(amount: number, currency = "USD"): string {
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency,
    minimumFractionDigits: 0,
    maximumFractionDigits: 2,
  }).format(amount);
}

export function formatNumber(num: number): string {
  if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`;
  if (num >= 1_000) return `${(num / 1_000).toFixed(1)}K`;
  return num.toString();
}

design-tokens.ts

design-tokens.ts
tsx
// Border radius
export const radius = {
  card: "rounded-xl",
  badge: "rounded-full",
  button: "rounded-lg",

Usage

app/billing/page.tsx
tsx
"use client";

import { useState } from "react";
import { CheckoutModal, type CheckoutPlan } from "@/components/billing";
import { Button } from "@/components/ui/button";

Props

PropTypeDefault
openbooleanRequired
onOpenChange(open: boolean) => voidRequired
planCheckoutPlanRequired
onComplete(details: BillingDetails) => void-
onCancel() => void-
classNamestring-