Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> CSS

Cách tạo form thanh toán (checkout form) responsive với CSS

Giới thiệu

Checkout form là form quan trọng nhất ảnh hưởng conversion rate. Yêu cầu: bố cục 2 cột (thông tin thanh toán + tóm tắt đơn hàng), validation chặt chẽ, trust signals, tự động định dạng thẻ tín dụng, responsive thành 1 cột trên mobile.

Ví dụ hoàn chỉnh - Checkout Form 2 cột

<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Responsive Checkout Form</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  <style>
    * { box-sizing: border-box; }
    body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f5f7fa; padding: 40px 20px; }
    .checkout-grid {
      max-width: 1000px;
      margin: 0 auto;
      display: grid;
      grid-template-columns: 1fr 380px;
      gap: 32px;
      align-items: start;
    }
    @media (max-width: 900px) {
      .checkout-grid { grid-template-columns: 1fr; }
      .order-summary { position: sticky; top: 20px; }
    }
    .form-section { background: #fff; border-radius: 16px; box-shadow: 0 4px 24px rgba(0,0,0,0.06); padding: 32px; border: 1px solid #eef0f2; }
    .section-title { display: flex; align-items: center; gap: 10px; margin: 0 0 24px; font-size: 20px; color: #1a1a1a; }
    .section-title i { color: #1eadef; }
    .form-row { display: flex; gap: 20px; margin-bottom: 20px; }
    .form-group { flex: 1; min-width: 0; }
    .form-group.full { flex-basis: 100%; }
    label { display: block; margin-bottom: 8px; font-weight: 500; font-size: 14px; color: #333; }
    .input-wrapper { position: relative; }
    input, select {
      width: 100%;
      padding: 14px 16px;
      font-size: 15px;
      border: 1.5px solid #e0e0e0;
      border-radius: 10px;
      background: #fff;
      transition: all 0.2s;
    }
    input:focus, select:focus { outline: none; border-color: #1eadef; box-shadow: 0 0 0 3px rgba(30,173,239,0.15); }
    .input-icon { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); color: #aaa; pointer-events: none; }
    .input-wrapper:focus-within .input-icon { color: #1eadef; }
    .input-with-icon input { padding-left: 48px; }
    .card-inputs { display: flex; gap: 12px; }
    .card-inputs .form-group { flex: 1; }
    .btn-checkout {
      width: 100%;
      padding: 16px;
      font-size: 16px;
      font-weight: 600;
      color: #fff;
      background: linear-gradient(135deg, #4caf50, #2e7d32);
      border: none;
      border-radius: 10px;
      cursor: pointer;
      margin-top: 8px;
      transition: transform 0.1s, box-shadow 0.2s;
    }
    .btn-checkout:hover { box-shadow: 0 6px 20px rgba(76,175,80,0.3); }
    .btn-checkout:active { transform: scale(0.98); }
    /* Order Summary */
    .order-summary {
      background: #fff;
      border-radius: 16px;
      box-shadow: 0 4px 24px rgba(0,0,0,0.06);
      padding: 24px;
      border: 1px solid #eef0f2;
      height: fit-content;
    }
    .summary-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid #eee; }
    .summary-header h3 { margin: 0; font-size: 18px; }
    .item-count { color: #666; font-size: 14px; font-weight: 400; }
    .cart-items { margin-bottom: 20px; }
    .cart-item { display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #f0f0f0; }
    .cart-item:last-child { border-bottom: none; }
    .item-info { display: flex; flex-direction: column; gap: 4px; }
    .item-name { font-weight: 500; font-size: 14px; }
    .item-meta { font-size: 12px; color: #888; }
    .item-price { font-weight: 600; color: #1a1a1a; white-space: nowrap; margin-left: 16px; }
    .summary-totals { border-top: 1px solid #eee; padding-top: 16px; }
    .total-row { display: flex; justify-content: space-between; margin-bottom: 10px; font-size: 14px; }
    .total-row.final { font-size: 16px; font-weight: 700; color: #1a1a1a; padding-top: 10px; border-top: 1px dashed #eee; margin-top: 10px; }
    .trust-badges { display: flex; gap: 16px; justify-content: center; margin-top: 24px; padding-top: 20px; border-top: 1px solid #eee; }
    .trust-badge { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #888; }
    .trust-badge i { color: #4caf50; }
    @media (max-width: 480px) {
      .form-section { padding: 20px; }
      .form-row { flex-direction: column; gap: 0; }
      .card-inputs { flex-direction: column; gap: 0; }
    }
  </style>
</head>
<body>
  <div class="checkout-grid">
    <div class="form-section">
      <h2 class="section-title"><i class="fas fa-user"></i> Thông tin thanh toán</h2>
      <form id="checkoutForm" novalidate>
        <div class="form-row">
          <div class="form-group">
            <label for="firstName">Họ</label>
            <div class="input-wrapper input-with-icon">
              <input type="text" id="firstName" name="firstName" placeholder="Nguyễn" required autocomplete="given-name">
              <i class="fas fa-user input-icon" aria-hidden="true"></i>
            </div>
          </div>
          <div class="form-group">
            <label for="lastName">Tên</label>
            <input type="text" id="lastName" name="lastName" placeholder="Văn An" required autocomplete="family-name">
          </div>
        </div>
        <div class="form-group full">
          <label for="email">Email</label>
          <div class="input-wrapper input-with-icon">
            <input type="email" id="email" name="email" placeholder="email@example.com" required autocomplete="email">
            <i class="fas fa-envelope input-icon" aria-hidden="true"></i>
          </div>
        </div>
        <div class="form-group full">
          <label for="address">Địa chỉ giao hàng</label>
          <input type="text" id="address" name="address" placeholder="Số nhà, tên đường" required autocomplete="street-address">
        </div>
        <div class="form-row">
          <div class="form-group">
            <label for="city">Tỉnh/Thành phố</label>
            <select id="city" name="city" required>
              <option value="">Chọn tỉnh/thành</option>
              <option>Hà Nội</option>
              <option>TP. Hồ Chí Minh</option>
              <option>Đà Nẵng</option>
            </select>
          </div>
          <div class="form-group">
            <label for="district">Quận/Huyện</label>
            <select id="district" name="district" required><option value="">Chọn quận/huyện</option></select>
          </div>
        </div>
        <div class="form-row">
          <div class="form-group">
            <label for="phone">Số điện thoại</label>
            <input type="tel" id="phone" name="phone" placeholder="09x xxx xxxx" required autocomplete="tel">
          </div>
          <div class="form-group">
            <label for="zip">Mã bưu chính</label>
            <input type="text" id="zip" name="zip" placeholder="100000" autocomplete="postal-code">
          </div>
        </div>
        <h2 class="section-title" style="margin-top:32px"><i class="fas fa-credit-card"></i> Thông tin thẻ</h2>
        <div class="form-group full">
          <label for="cardName">Tên trên thẻ</label>
          <input type="text" id="cardName" name="cardName" placeholder="NGUYEN VAN AN" required autocomplete="cc-name" style="text-transform:uppercase">
        </div>
        <div class="form-group full">
          <label for="cardNumber">Số thẻ</label>
          <div class="input-wrapper input-with-icon">
            <input type="text" id="cardNumber" name="cardNumber" placeholder="1234 5678 9012 3456" required autocomplete="cc-number" maxlength="19" inputmode="numeric">
            <i class="fas fa-credit-card input-icon" aria-hidden="true"></i>
          </div>
        </div>
        <div class="form-row card-inputs">
          <div class="form-group">
            <label for="expiry">Hết hạn (MM/YY)</label>
            <input type="text" id="expiry" name="expiry" placeholder="MM/YY" required autocomplete="cc-exp" maxlength="5" inputmode="numeric">
          </div>
          <div class="form-group">
            <label for="cvv">CVV</label>
            <input type="text" id="cvv" name="cvv" placeholder="123" required autocomplete="cc-csc" maxlength="4" inputmode="numeric">
          </div>
        </div>
        <button type="submit" class="btn-checkout">Thanh toán 299.000₫</button>
      </form>
    </div>
    <aside class="order-summary">
      <div class="summary-header">
        <h3>Đơn hàng</h3>
        <span class="item-count">2 sản phẩm</span>
      </div>
      <div class="cart-items">
        <div class="cart-item">
          <div class="item-info">
            <span class="item-name">Áo thun cao cấp</span>
            <span class="item-meta">Size M × 1</span>
          </div>
          <span class="item-price">199.000₫</span>
        </div>
        <div class="cart-item">
          <div class="item-info">
            <span class="item-name">Mũ lưỡi trai</span>
            <span class="item-meta">Mặc định × 1</span>
          </div>
          <span class="item-price">80.000₫</span>
        </div>
      </div>
      <div class="summary-totals">
        <div class="total-row"><span>Tạm tính</span><span>279.000₫</span></div>
        <div class="total-row"><span>Vận chuyển</span><span>20.000₫</span></div>
        <div class="total-row"><span>Giảm giá</span><span style="color:#4caf50">-0₫</span></div>
        <div class="total-row final"><span>Tổng cộng</span><span>299.000₫</span></div>
      </div>
      <div class="trust-badges">
        <span class="trust-badge"><i class="fas fa-shield-alt"></i> Bảo mật SSL</span>
        <span class="trust-badge"><i class="fas fa-lock"></i> Mã hóa 256-bit</span>
        <span class="trust-badge"><i class="fas fa-undo"></i> Đổi trả 30 ngày</span>
      </div>
    </aside>
  </div>
  <script>
    // Auto-format card number: 1234 5678 9012 3456
    document.getElementById('cardNumber').addEventListener('input', (e) => {
      let v = e.target.value.replace(/\D/g, '').replace(/(.{4})/g, '$1 ').trim();
      e.target.value = v.substring(0, 19);
    });
    // Auto-format expiry: MM/YY
    document.getElementById('expiry').addEventListener('input', (e) => {
      let v = e.target.value.replace(/\D/g, '');
      if (v.length >= 2) v = v.slice(0,2) + '/' + v.slice(2,4);
      e.target.value = v;
    });
    // Detect card type by first digits
    document.getElementById('cardNumber').addEventListener('input', (e) => {
      const icon = e.target.nextElementSibling;
      const val = e.target.value.replace(/\s/g, '');
      if (val.startsWith('4')) icon.className = 'fas fa-cc-visa input-icon';
      else if (val.startsWith('5')) icon.className = 'fas fa-cc-mastercard input-icon';
      else if (val.startsWith('3')) icon.className = 'fas fa-cc-amex input-icon';
      else icon.className = 'fas fa-credit-card input-icon';
    });
  </script>
</body>
</html>

Yếu tố then chốt của Checkout Form

Yếu tố Mô tả
Layout 2 cộtForm trái, Order summary phải (sticky trên mobile).
Auto-format cardTự thêm space sau 4 chữ số, detect Visa/Master/Amex icon.
Expiry MM/YYTự thêm slash "/", chỉ cho phép số.
Autocomplete attrscc-name, cc-number, cc-exp, cc-csc, postal-code - giúp trình duyệt autofill.
Trust badgesSSL, mã hóa, đổi trả - giảm lo ngại bảo mật.
Sticky summaryMobile: summary dính trên cùng khi scroll form dài.

Mẹo nâng cao

  • Dùng thư viện Cleave.js hoặc IMask cho input mask phức tạp.
  • Tích hợp Stripe Elements / PayPal SDK - không tự xử lý số thẻ raw (PCI DSS).
  • Luôn hiển thị tổng tiền cuối cùng rõ ràng trước khi bấm thanh toán.
  • Cho phép guest checkout (không bắt buộc đăng ký).