Checkout Steps Processus Commande Bootstrap 5

Extraits & Composants HTML 10/04/2026 12:00:00 angularforall.com
Bootstrap 5 Checkout Stepper E Commerce Formulaire Multi Etapes Template Html Css Js

Processus de commande multi-étapes Bootstrap 5 : stepper visuel, validation par étape, récapitulatif panier et confirmation finale responsive.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8" />
  <meta name="copyright" content="AngularForAll" />
  <meta name="author" content="AngularForAll" />
  <meta name="robots" content="noindex, nofollow" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="Cache-Control" content="public, max-age=604800" />
  <title>Snippets Checkout Step Boostrap5 2026 05020023 | AngularForAll</title>
<!-- Bootstrap 5 CSS -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
  
  <!-- Bootstrap Icons -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
  
  <style>
    :root {
      --step-primary: #4361ee;
      --step-success: #06d6a0;
      --step-pending: #e9ecef;
      --step-text: #6c757d;
      --step-active-text: #212529;
    }
    
    body {
      background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
      min-height: 100vh;
      font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
    }
    
    /* ========== CONTENEUR PRINCIPAL ========== */
    .checkout-container {
      max-width: 900px;
      margin: 0 auto;
      padding: 2rem 1rem;
    }
    
    /* ========== CARTE PRINCIPALE ========== */
    .checkout-card {
      background: #ffffff;
      border-radius: 24px;
      border: none;
      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.05);
      overflow: hidden;
    }
    
    .checkout-header {
      background: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%);
      padding: 2rem 2rem 1.5rem;
      color: white;
    }
    
    .checkout-body {
      padding: 2rem;
    }
    
    /* ========== INDICATEUR D'ÉTAPES ========== */
    .steps-indicator {
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
      padding: 0 0.5rem;
      margin-bottom: 2.5rem;
    }
    
    /* Ligne de connexion entre les étapes */
    .steps-line {
      position: absolute;
      top: 28px;
      left: calc(16.67% + 10px);
      right: calc(16.67% + 10px);
      height: 4px;
      background: var(--step-pending);
      border-radius: 2px;
      z-index: 1;
      transition: background 0.4s ease;
    }
    
    .steps-line-progress {
      position: absolute;
      top: 0;
      left: 0;
      height: 100%;
      background: linear-gradient(90deg, var(--step-primary), var(--step-success));
      border-radius: 2px;
      transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
      z-index: 2;
    }
    
    /* Étape individuelle */
    .step-item {
      display: flex;
      flex-direction: column;
      align-items: center;
      position: relative;
      z-index: 3;
      flex: 1;
      cursor: pointer;
      transition: transform 0.2s ease;
    }
    
    .step-item:hover {
      transform: translateY(-2px);
    }
    
    .step-circle {
      width: 56px;
      height: 56px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.4rem;
      font-weight: 700;
      margin-bottom: 0.75rem;
      transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
      position: relative;
      background: var(--step-pending);
      color: var(--step-text);
      border: 3px solid transparent;
    }
    
    /* Étape active */
    .step-item.active .step-circle {
      background: white;
      color: var(--step-primary);
      border-color: var(--step-primary);
      box-shadow: 0 0 0 8px rgba(67, 97, 238, 0.15), 0 8px 20px rgba(67, 97, 238, 0.2);
      transform: scale(1.05);
    }
    
    /* Étape complétée */
    .step-item.completed .step-circle {
      background: var(--step-success);
      color: white;
      border-color: var(--step-success);
      box-shadow: 0 0 0 8px rgba(6, 214, 160, 0.1), 0 4px 12px rgba(6, 214, 160, 0.2);
    }
    
    .step-item.completed .step-circle .step-icon::before {
      content: '\F26E'; /* Bootstrap Icons check */
      font-family: 'Bootstrap Icons';
      font-weight: bold;
    }
    
    .step-label {
      font-size: 0.85rem;
      font-weight: 600;
      color: var(--step-text);
      text-transform: uppercase;
      letter-spacing: 0.5px;
      transition: color 0.3s ease;
      text-align: center;
    }
    
    .step-item.active .step-label {
      color: var(--step-primary);
      font-weight: 700;
    }
    
    .step-item.completed .step-label {
      color: var(--step-success);
    }
    
    .step-sublabel {
      font-size: 0.7rem;
      color: #adb5bd;
      margin-top: 0.15rem;
      text-align: center;
    }
    
    /* ========== CONTENU DES ÉTAPES ========== */
    .step-content {
      min-height: 300px;
      padding: 1.5rem;
      background: #f8f9fa;
      border-radius: 16px;
      margin-top: 1rem;
    }
    
    .step-content h4 {
      font-weight: 700;
      color: #212529;
      margin-bottom: 1rem;
    }
    
    .step-content p {
      color: #6c757d;
      margin-bottom: 1.5rem;
    }
    
    /* ========== BOUTONS DE NAVIGATION ========== */
    .btn-prev {
      background: white;
      border: 2px solid #dee2e6;
      color: #495057;
      border-radius: 12px;
      padding: 0.6rem 1.5rem;
      font-weight: 600;
      transition: all 0.2s ease;
    }
    
    .btn-prev:hover {
      background: #f1f3f5;
      border-color: #adb5bd;
    }
    
    .btn-next {
      background: var(--step-primary);
      border: 2px solid var(--step-primary);
      color: white;
      border-radius: 12px;
      padding: 0.6rem 1.8rem;
      font-weight: 600;
      transition: all 0.2s ease;
      box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
    }
    
    .btn-next:hover {
      background: #3a56d4;
      border-color: #3a56d4;
      box-shadow: 0 6px 20px rgba(67, 97, 238, 0.4);
      transform: translateY(-1px);
    }
    
    .btn-success-checkout {
      background: var(--step-success);
      border: 2px solid var(--step-success);
      color: white;
      border-radius: 12px;
      padding: 0.6rem 2rem;
      font-weight: 600;
      box-shadow: 0 4px 15px rgba(6, 214, 160, 0.3);
      transition: all 0.2s ease;
    }
    
    .btn-success-checkout:hover {
      background: #05b88a;
      border-color: #05b88a;
      box-shadow: 0 6px 20px rgba(6, 214, 160, 0.4);
      color: white;
    }
    
    /* ========== LISTE DE PRODUITS (ÉTAPE PANIER) ========== */
    .cart-item {
      display: flex;
      align-items: center;
      gap: 1rem;
      padding: 1rem;
      background: white;
      border-radius: 12px;
      margin-bottom: 0.75rem;
      border: 1px solid #e9ecef;
      transition: box-shadow 0.2s ease;
    }
    
    .cart-item:hover {
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
    }
    
    .cart-item-img {
      width: 60px;
      height: 60px;
      border-radius: 10px;
      object-fit: cover;
      background: #f1f3f5;
    }
    
    .cart-item-info {
      flex: 1;
    }
    
    .cart-item-title {
      font-weight: 600;
      color: #212529;
      margin-bottom: 0.15rem;
    }
    
    .cart-item-meta {
      font-size: 0.8rem;
      color: #adb5bd;
    }
    
    .cart-item-price {
      font-weight: 700;
      color: #212529;
    }
    
    /* ========== RESUME (ÉTAPE PAIEMENT) ========== */
    .summary-line {
      display: flex;
      justify-content: space-between;
      padding: 0.5rem 0;
      border-bottom: 1px solid #e9ecef;
      font-size: 0.95rem;
    }
    
    .summary-total {
      font-weight: 700;
      font-size: 1.2rem;
      border-bottom: none;
      color: #212529;
    }
    
    /* ========== RESPONSIVE ========== */
    @media (max-width: 768px) {
      .checkout-header {
        padding: 1.5rem 1.5rem 1.2rem;
      }
      
      .checkout-body {
        padding: 1.5rem;
      }
      
      .step-circle {
        width: 44px;
        height: 44px;
        font-size: 1.1rem;
      }
      
      .steps-line {
        top: 22px;
      }
      
      .step-label {
        font-size: 0.7rem;
      }
      
      .step-content {
        padding: 1rem;
      }
    }
    
    @media (max-width: 576px) {
      .steps-indicator {
        flex-direction: column;
        gap: 1.5rem;
        align-items: flex-start;
        padding-left: 1rem;
      }
      
      .step-item {
        flex-direction: row;
        gap: 1rem;
        flex: none;
        width: 100%;
      }
      
      .step-circle {
        margin-bottom: 0;
        width: 40px;
        height: 40px;
        font-size: 1rem;
      }
      
      .steps-line {
        display: none;
      }
      
      .step-item::after {
        content: '';
        position: absolute;
        left: 19px;
        top: 42px;
        bottom: -24px;
        width: 3px;
        background: var(--step-pending);
        z-index: 0;
      }
      
      .step-item:last-child::after {
        display: none;
      }
      
      .step-item.completed::after {
        background: var(--step-success);
      }
      
      .step-item.active::after {
        background: linear-gradient(to bottom, var(--step-primary), var(--step-pending));
      }
    }
  </style>
</head>
<body>

  <div class="checkout-container">
    
    <!-- Carte principale -->
    <div class="checkout-card">
      
      <!-- En-tête -->
      <div class="checkout-header text-center">
        <h2 class="fw-bold mb-1">
          <i class="bi bi-cart-check me-2"></i>Finaliser la commande
        </h2>
        <p class="mb-0 opacity-75 small">Complétez les étapes ci-dessous</p>
      </div>
      
      <!-- Corps -->
      <div class="checkout-body">
        
        <!-- ========== INDICATEUR D'ÉTAPES ========== -->
        <div class="steps-indicator" id="stepsIndicator">
          <!-- Ligne de fond -->
          <div class="steps-line">
            <div class="steps-line-progress" id="stepsLineProgress" style="width: 0%;"></div>
          </div>
          
          <!-- Étape 1 : Panier -->
          <div class="step-item active" data-step="1" onclick="goToStep(1)">
            <div class="step-circle">
              <span class="step-icon">1</span>
            </div>
            <div>
              <div class="step-label">
                <i class="bi bi-cart3 me-1"></i>Panier
              </div>
              <div class="step-sublabel">Vérifier les articles</div>
            </div>
          </div>
          
          <!-- Étape 2 : Livraison -->
          <div class="step-item" data-step="2" onclick="goToStep(2)">
            <div class="step-circle">
              <span class="step-icon">2</span>
            </div>
            <div>
              <div class="step-label">
                <i class="bi bi-truck me-1"></i>Livraison
              </div>
              <div class="step-sublabel">Adresse et mode</div>
            </div>
          </div>
          
          <!-- Étape 3 : Paiement -->
          <div class="step-item" data-step="3" onclick="goToStep(3)">
            <div class="step-circle">
              <span class="step-icon">3</span>
            </div>
            <div>
              <div class="step-label">
                <i class="bi bi-credit-card me-1"></i>Paiement
              </div>
              <div class="step-sublabel">Finaliser l'achat</div>
            </div>
          </div>
        </div>
        
        <!-- ========== CONTENU DYNAMIQUE ========== -->
        <div class="step-content" id="stepContent">
          <!-- Rempli dynamiquement par JavaScript -->
        </div>
        
        <!-- ========== BOUTONS DE NAVIGATION ========== -->
        <div class="d-flex justify-content-between align-items-center mt-4" id="navigationButtons">
          <button class="btn btn-prev" id="btnPrev" onclick="prevStep()" disabled>
            <i class="bi bi-arrow-left me-1"></i>Précédent
          </button>
          <span class="text-muted small" id="stepCounter">Étape 1/3</span>
          <button class="btn btn-next" id="btnNext" onclick="nextStep()">
            Suivant<i class="bi bi-arrow-right ms-1"></i>
          </button>
        </div>
        
      </div>
    </div>
    
    <!-- Indicateur de confiance -->
    <div class="text-center mt-3">
      <small class="text-muted">
        <i class="bi bi-shield-check text-success me-1"></i>
        Paiement 100% sécurisé • SSL crypté
      </small>
    </div>
  </div>

  <!-- Bootstrap 5 JS Bundle -->
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
  
  <!-- Script du CheckoutSteps -->
  <script>
    (function() {
      // ============ CONFIGURATION ============
      const TOTAL_STEPS = 3;
      let currentStep = 1; // 1 = Panier, 2 = Livraison, 3 = Paiement
      
      // Historique de complétion (empêche d'aller à l'étape 3 sans avoir validé la 2, etc.)
      const completedSteps = new Set();
      
      // ============ ÉLÉMENTS DOM ============
      const stepItems = document.querySelectorAll('.step-item');
      const stepsLineProgress = document.getElementById('stepsLineProgress');
      const stepContent = document.getElementById('stepContent');
      const btnPrev = document.getElementById('btnPrev');
      const btnNext = document.getElementById('btnNext');
      const stepCounter = document.getElementById('stepCounter');
      
      // ============ CONTENUS DES ÉTAPES ============
      const stepContents = {
        1: `
          <h4><i class="bi bi-cart3 me-2 text-primary"></i>Votre Panier</h4>
          <p>Vérifiez les articles avant de continuer.</p>
          
          <div class="cart-item">
            <img src="https://placehold.co/60x60/e9ecef/495057?text=S1" alt="Produit 1" class="cart-item-img">
            <div class="cart-item-info">
              <div class="cart-item-title">Chaussures Sportswear Air Max</div>
              <div class="cart-item-meta">Taille : 42 • Couleur : Noir/Blanc</div>
            </div>
            <div class="cart-item-price">129,99 €</div>
          </div>
          
          <div class="cart-item">
            <img src="https://placehold.co/60x60/e9ecef/495057?text=S2" alt="Produit 2" class="cart-item-img">
            <div class="cart-item-info">
              <div class="cart-item-title">Montre Connectée FitPro X200</div>
              <div class="cart-item-meta">Bracelet silicone • Noir</div>
            </div>
            <div class="cart-item-price">249,99 €</div>
          </div>
          
          <hr class="my-3">
          <div class="d-flex justify-content-between fw-bold">
            <span>Total (2 articles)</span>
            <span class="text-primary">379,98 €</span>
          </div>
        `,
        2: `
          <h4><i class="bi bi-truck me-2 text-primary"></i>Adresse de Livraison</h4>
          <p>Choisissez votre adresse et le mode de livraison.</p>
          
          <div class="row g-3">
            <div class="col-md-6">
              <label class="form-label fw-semibold">Prénom</label>
              <input type="text" class="form-control rounded-3" placeholder="Jean" value="Thomas">
            </div>
            <div class="col-md-6">
              <label class="form-label fw-semibold">Nom</label>
              <input type="text" class="form-control rounded-3" placeholder="Dupont" value="Martin">
            </div>
            <div class="col-12">
              <label class="form-label fw-semibold">Adresse</label>
              <input type="text" class="form-control rounded-3" placeholder="123 rue Example" value="15 Avenue des Champs-Élysées">
            </div>
            <div class="col-md-4">
              <label class="form-label fw-semibold">Code Postal</label>
              <input type="text" class="form-control rounded-3" placeholder="75000" value="75008">
            </div>
            <div class="col-md-8">
              <label class="form-label fw-semibold">Ville</label>
              <input type="text" class="form-control rounded-3" placeholder="Paris" value="Paris">
            </div>
            <div class="col-12">
              <label class="form-label fw-semibold">Mode de livraison</label>
              <div class="form-check p-3 border rounded-3 mb-2 bg-white">
                <input class="form-check-input" type="radio" name="delivery" id="standard" checked>
                <label class="form-check-label fw-semibold" for="standard">
                  Standard — <span class="text-muted">3-5 jours ouvrés</span>
                  <br><small class="text-success">Gratuit</small>
                </label>
              </div>
              <div class="form-check p-3 border rounded-3 bg-white">
                <input class="form-check-input" type="radio" name="delivery" id="express">
                <label class="form-check-label fw-semibold" for="express">
                  Express — <span class="text-muted">1-2 jours ouvrés</span>
                  <br><small class="text-muted">+ 9,99 €</small>
                </label>
              </div>
            </div>
          </div>
        `,
        3: `
          <h4><i class="bi bi-credit-card me-2 text-primary"></i>Paiement Sécurisé</h4>
          <p>Choisissez votre méthode de paiement.</p>
          
          <div class="mb-4">
            <div class="form-check p-3 border rounded-3 mb-2 bg-white">
              <input class="form-check-input" type="radio" name="payment" id="card" checked>
              <label class="form-check-label fw-semibold" for="card">
                <i class="bi bi-credit-card-2-front me-2"></i>Carte Bancaire
              </label>
            </div>
            <div class="form-check p-3 border rounded-3 mb-2 bg-white">
              <input class="form-check-input" type="radio" name="payment" id="paypal">
              <label class="form-check-label fw-semibold" for="paypal">
                <i class="bi bi-paypal me-2"></i>PayPal
              </label>
            </div>
            <div class="form-check p-3 border rounded-3 bg-white">
              <input class="form-check-input" type="radio" name="payment" id="applepay">
              <label class="form-check-label fw-semibold" for="applepay">
                <i class="bi bi-apple me-2"></i>Apple Pay
              </label>
            </div>
          </div>
          
          <div class="bg-white p-3 rounded-3 border">
            <div class="summary-line"><span>Sous-total</span><span>379,98 €</span></div>
            <div class="summary-line"><span>Livraison</span><span class="text-success">Gratuite</span></div>
            <div class="summary-line"><span>TVA (20%)</span><span>63,33 €</span></div>
            <div class="summary-line summary-total mt-2 pt-2"><span>Total</span><span class="text-primary">379,98 €</span></div>
          </div>
        `
      };
      
      // ============ FONCTIONS ============
      function updateStepsUI() {
        // Mise à jour des classes des étapes
        stepItems.forEach((item, index) => {
          const stepNum = index + 1;
          item.classList.remove('active', 'completed');
          
          if (stepNum < currentStep || completedSteps.has(stepNum)) {
            item.classList.add('completed');
          } else if (stepNum === currentStep) {
            item.classList.add('active');
          }
        });
        
        // Mise à jour de la barre de progression
        let progressPercent = 0;
        if (currentStep === 2) progressPercent = 50;
        if (currentStep === 3) progressPercent = 100;
        stepsLineProgress.style.width = progressPercent + '%';
        
        // Mise à jour du contenu
        stepContent.innerHTML = stepContents[currentStep];
        
        // Mise à jour des boutons
        btnPrev.disabled = (currentStep === 1);
        btnPrev.style.opacity = currentStep === 1 ? '0.5' : '1';
        btnPrev.style.cursor = currentStep === 1 ? 'not-allowed' : 'pointer';
        
        if (currentStep === TOTAL_STEPS) {
          btnNext.style.display = 'none';
          // Ajouter un bouton de confirmation
          if (!document.getElementById('btnConfirm')) {
            const confirmBtn = document.createElement('button');
            confirmBtn.id = 'btnConfirm';
            confirmBtn.className = 'btn btn-success-checkout';
            confirmBtn.innerHTML = '<i class="bi bi-check-circle me-1"></i>Confirmer la commande';
            confirmBtn.onclick = confirmOrder;
            btnNext.parentNode.insertBefore(confirmBtn, btnNext.nextSibling);
          }
        } else {
          btnNext.style.display = 'inline-block';
          btnNext.innerHTML = 'Suivant<i class="bi bi-arrow-right ms-1"></i>';
          const confirmBtn = document.getElementById('btnConfirm');
          if (confirmBtn) confirmBtn.remove();
        }
        
        // Mise à jour du compteur
        stepCounter.textContent = `Étape ${currentStep}/${TOTAL_STEPS}`;
      }
      
      function goToStep(step) {
        // On peut toujours revenir en arrière
        if (step < currentStep) {
          currentStep = step;
          updateStepsUI();
          return;
        }
        
        // Pour avancer, il faut que l'étape précédente soit complétée
        if (step > currentStep) {
          // Vérifier si on peut avancer (l'étape actuelle est complétée ou on avance d'une seule étape)
          if (step === currentStep + 1) {
            // Marquer l'étape actuelle comme complétée avant d'avancer
            completedSteps.add(currentStep);
            currentStep = step;
            updateStepsUI();
          } else if (completedSteps.has(step - 1)) {
            // L'étape précédente est complétée, on peut y aller
            currentStep = step;
            updateStepsUI();
          }
          // Sinon, on ignore le clic (l'utilisateur doit suivre l'ordre)
        }
      }
      
      function nextStep() {
        if (currentStep < TOTAL_STEPS) {
          completedSteps.add(currentStep);
          currentStep++;
          updateStepsUI();
        }
      }
      
      function prevStep() {
        if (currentStep > 1) {
          currentStep--;
          updateStepsUI();
        }
      }
      
      function confirmOrder() {
        // Animation de confirmation
        const confirmBtn = document.getElementById('btnConfirm');
        if (confirmBtn) {
          confirmBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Traitement...';
          confirmBtn.disabled = true;
          
          setTimeout(() => {
            // Marquer l'étape 3 comme complétée
            completedSteps.add(3);
            updateStepsUI();
            
            // Afficher un message de succès
            stepContent.innerHTML = `
              <div class="text-center py-4">
                <div class="mb-3">
                  <i class="bi bi-check-circle-fill text-success" style="font-size: 4rem;"></i>
                </div>
                <h4 class="fw-bold">Commande confirmée !</h4>
                <p class="text-muted mb-1">Votre commande #CMD-2026-0421 a bien été enregistrée.</p>
                <p class="text-muted small">Un email de confirmation a été envoyé à thomas.martin@email.fr</p>
                <button class="btn btn-primary rounded-3 mt-3" onclick="window.location.reload()">
                  <i class="bi bi-arrow-repeat me-1"></i>Nouvelle commande
                </button>
              </div>
            `;
            
            // Cacher les boutons de navigation
            document.getElementById('navigationButtons').style.display = 'none';
            
            // Marquer toutes les étapes comme complétées
            stepItems.forEach(item => item.classList.add('completed'));
            stepsLineProgress.style.width = '100%';
          }, 1500);
        }
      }
      
      // ============ RENDU GLOBAL DES FONCTIONS ============
      window.goToStep = goToStep;
      window.nextStep = nextStep;
      window.prevStep = prevStep;
      window.confirmOrder = confirmOrder;
      
      // ============ INITIALISATION ============
      updateStepsUI();
      
      console.log('✅ CheckoutSteps initialisé • Étape actuelle :', currentStep);
    })();
  </script>
</body>
</html>

Télécharger le fichier source

Partager