SnippetUI

Formulaire multi-étapes

Interface utilisateur qui bascule entre plusieurs étapes (avec indicateur de progression), souvent utilisée sur les écrans d'inscription, etc.

HTMLTailwind CSSJavaScript

Il s’agit d’un composant d’interface utilisateur de formulaire en plusieurs étapes (format assistant) utilisé sur des écrans avec de nombreux éléments d’entrée, tels que l’enregistrement des utilisateurs et les questionnaires. Il comporte un indicateur de progression qui transmet visuellement vos progrès à chaque étape et des animations de glissement fluides.

Aperçu

1
Account
2
Personal
3
Review

Account Information

Please enter the information used for login.

Personal Information

Please tell us your basic information.

Confirm your input

You will be registered with the following information. Is this okay?

Email Addressyou@example.com
氏First NameJohn Doe
Phone Number090-1234-5678

Cas d’utilisation et fonctionnalités

  • Inscription/intégration des utilisateurs : en demandant aux utilisateurs de saisir les informations dans des parties séparées, nous réduisons les obstacles psychologiques pour les utilisateurs et les empêchons de quitter le site à mi-chemin (abandon du panier ou abandon du formulaire).
  • Organisation de saisies complexes : réduit la charge cognitive de l’utilisateur en divisant les tâches contenant de nombreux éléments en groupes logiques, comme la saisie d’informations sur l’adresse de livraison ou de questionnaires.
  • Visualisation de la progression : L’indicateur de progression en haut de l’écran permet aux utilisateurs de voir en un coup d’œil où ils se trouvent actuellement et combien de temps il faudra pour terminer, leur offrant ainsi une tranquillité d’esprit.

Points clés du design et de l’UX (Design & UX)

  • Animation de diapositive fluide : lors du changement d’étape, le contenu précédent et suivant glisse tout en fondu d’un côté à l’autre, créant une continuité spatiale.
  • Effacer les commentaires sur l’état : les étapes terminées sont marquées d’une coche et les étapes en cours sont mises en évidence avec l’utilitaire « ring » pour communiquer intuitivement l’état actuel.
  • Conception du bouton qui encourage l’action : Lorsque l’étape finale est atteinte, la couleur du bouton « Suivant » passe au vert (« émeraude »), ce qui signifie l’achèvement, et en même temps l’étiquette change en « Soumettre », encourageant clairement l’action finale.

Comment personnaliser (Guide de personnalisation)

  • Compatibilité avec les couleurs de la marque : Vous pouvez facilement personnaliser la tonalité de couleur en remplaçant simplement les spécifications de couleur telles que « indigo-600 » dans le code par la couleur du thème de votre projet (par exemple « bleu-600 » ou « rose-500 »).
  • Modification du nombre d’étapes : Vous pouvez modifier le nombre d’étapes de manière flexible en augmentant ou en diminuant l’ensemble de <div class="msf-step-indicator">...</div> et <div class="msf-step-content">...</div> dans le HTML, et en les combinant avec const steps = [1, 2, 3]; dans le JavaScript.
  • Intégration de validation : en implémentant des vérifications d’entrée (éléments requis, vérifications de format, etc.) pour chaque étape en cours dans l’événement de clic sur le bouton « Suivant » (btnNext.addEventListener), et en ajoutant un contrôle pour exécuter currentStep++ s’il n’y a pas d’erreurs, il peut être utilisé en fonctionnement réel.

Code d’implémentation (Implémentation)

<div class="multi-step-form-wrapper bg-gray-50 dark:bg-zinc-950 flex items-center justify-center p-6 min-h-[600px] font-sans">
<div class="bg-white dark:bg-zinc-900 rounded-2xl shadow-xl w-full max-w-lg p-8 relative overflow-hidden border border-gray-100 dark:border-zinc-800">
  <!-- Progress Bar -->
  <div class="flex justify-between items-center mb-10 relative">
    <div class="absolute left-0 top-1/2 transform -translate-y-1/2 w-full h-1.5 bg-gray-200 dark:bg-zinc-800 z-0 rounded-full"></div>
    <div id="msf-progress-bar" class="absolute left-0 top-1/2 transform -translate-y-1/2 h-1.5 bg-indigo-600 z-0 transition-all duration-500 ease-out rounded-full" style="width: 0%;"></div>
    
    <!-- Step 1 -->
    <div class="relative z-10 flex flex-col items-center">
      <div class="msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-indigo-600 text-white shadow-md border-4 border-white dark:border-zinc-900 ring-4 ring-indigo-100" data-step="1">1</div>
      <span class="msf-step-label text-xs font-bold text-indigo-600 mt-2 transition-colors duration-300 tracking-wide uppercase">Account</span>
    </div>
    <!-- Step 2 -->
    <div class="relative z-10 flex flex-col items-center">
      <div class="msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-gray-200 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 dark:text-gray-500 border-4 border-white dark:border-zinc-900" data-step="2">2</div>
      <span class="msf-step-label text-xs font-medium text-gray-400 dark:text-gray-500 mt-2 transition-colors duration-300 tracking-wide uppercase">Personal</span>
    </div>
    <!-- Step 3 -->
    <div class="relative z-10 flex flex-col items-center">
      <div class="msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-gray-200 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 dark:text-gray-500 border-4 border-white dark:border-zinc-900" data-step="3">3</div>
      <span class="msf-step-label text-xs font-medium text-gray-400 dark:text-gray-500 mt-2 transition-colors duration-300 tracking-wide uppercase">Review</span>
    </div>
  </div>

  <!-- Form Steps -->
  <div class="relative h-[280px] overflow-visible">
    <!-- Step 1 Content -->
    <div class="msf-step-content absolute w-full transition-all duration-500 transform translate-x-0 opacity-100" data-step="1">
      <h2 class="text-2xl font-extrabold text-gray-900 dark:text-white mb-2">Account Information</h2>
      <p class="text-sm text-gray-500 dark:text-gray-400 dark:text-gray-500 mb-6">Please enter the information used for login.</p>
      <div class="space-y-4">
        <div>
          <label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">Email Address</label>
          <input type="email" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="you@example.com" />
        </div>
        <div>
          <label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">Password</label>
          <input type="password" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="••••••••" />
        </div>
      </div>
    </div>

    <!-- Step 2 Content -->
    <div class="msf-step-content absolute w-full transition-all duration-500 transform translate-x-12 opacity-0 pointer-events-none" data-step="2">
      <h2 class="text-2xl font-extrabold text-gray-900 dark:text-white mb-2">Personal Information</h2>
      <p class="text-sm text-gray-500 dark:text-gray-400 dark:text-gray-500 mb-6">Please tell us your basic information.</p>
      <div class="space-y-4">
        <div class="grid grid-cols-2 gap-4">
          <div>
            <label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">Last Name</label>
            <input type="text" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="Doe" />
          </div>
          <div>
            <label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">First Name</label>
            <input type="text" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="John" />
          </div>
        </div>
        <div>
          <label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">Phone Number</label>
          <input type="tel" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="090-1234-5678" />
        </div>
      </div>
    </div>

    <!-- Step 3 Content -->
    <div class="msf-step-content absolute w-full transition-all duration-500 transform translate-x-12 opacity-0 pointer-events-none" data-step="3">
      <h2 class="text-2xl font-extrabold text-gray-900 dark:text-white mb-2">Confirm your input</h2>
      <p class="text-sm text-gray-500 dark:text-gray-400 dark:text-gray-500 mb-6">You will be registered with the following information. Is this okay?</p>
      <div class="bg-gray-50 dark:bg-zinc-950/80 backdrop-blur-sm rounded-xl p-5 border border-gray-100 dark:border-zinc-800 shadow-inner space-y-4 text-sm">
        <div class="flex justify-between items-center border-b border-gray-200 dark:border-zinc-800 pb-3">
          <span class="text-gray-500 dark:text-gray-400 dark:text-gray-500 font-medium">Email Address</span>
          <span class="font-bold text-gray-800 dark:text-gray-200">you@example.com</span>
        </div>
        <div class="flex justify-between items-center border-b border-gray-200 dark:border-zinc-800 pb-3">
          <span class="text-gray-500 dark:text-gray-400 dark:text-gray-500 font-medium">氏First Name</span>
          <span class="font-bold text-gray-800 dark:text-gray-200">John Doe</span>
        </div>
        <div class="flex justify-between items-center">
          <span class="text-gray-500 dark:text-gray-400 dark:text-gray-500 font-medium">Phone Number</span>
          <span class="font-bold text-gray-800 dark:text-gray-200">090-1234-5678</span>
        </div>
      </div>
    </div>
  </div>

  <!-- Buttons -->
  <div class="flex justify-between mt-8 pt-6 border-t border-gray-100 dark:border-zinc-800 relative z-10">
    <button id="msf-btn-prev" class="px-6 py-2.5 border border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-300 font-bold rounded-xl hover:bg-gray-50 dark:bg-zinc-950 hover:text-indigo-600 focus:outline-none focus:ring-2 focus:ring-gray-200 transition-all duration-300 opacity-0 pointer-events-none disabled:opacity-50 flex items-center justify-center gap-2">
      <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
      Back
    </button>
    <button id="msf-btn-next" class="px-6 py-2.5 bg-indigo-600 text-white font-bold rounded-xl hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-all duration-300 ml-auto shadow-[0_4px_14px_0_rgba(79,70,229,0.39)] hover:shadow-[0_6px_20px_rgba(79,70,229,0.23)] hover:-translate-y-0.5 flex items-center justify-center gap-2 active:translate-y-0">
      Next
      <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
    </button>
  </div>
</div>
</div>

<script>
(function() {
  const steps = [1, 2, 3];
  let currentStep = 1;
  
  const progressBar = document.getElementById('msf-progress-bar');
  const indicators = document.querySelectorAll('.msf-step-indicator');
  const labels = document.querySelectorAll('.msf-step-label');
  const contents = document.querySelectorAll('.msf-step-content');
  const btnPrev = document.getElementById('msf-btn-prev');
  const btnNext = document.getElementById('msf-btn-next');
  
  if (!progressBar) return;

  function validateStep() {
    if (currentStep === steps.length) return true;
    const currentContent = document.querySelector('.msf-step-content[data-step="' + currentStep + '"]');
    const inputs = currentContent.querySelectorAll('input[required]');
    let isValid = true;
    inputs.forEach(input => {
      if (!input.checkValidity()) {
        input.reportValidity();
        isValid = false;
      }
    });
    return isValid;
  }

  function updateUI() {
    const progressPercentage = ((currentStep - 1) / (steps.length - 1)) * 100;
    progressBar.style.width = progressPercentage + '%';
    
    indicators.forEach((ind, index) => {
      const stepNum = index + 1;
      const label = labels[index];
      
      if (stepNum < currentStep) {
        ind.className = "msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-indigo-600 text-white shadow-md border-4 border-white dark:border-zinc-900";
        ind.innerHTML = '<svg class="w-5 h-5 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path></svg>';
        label.className = "msf-step-label text-xs font-bold text-indigo-600 mt-2 transition-colors duration-300 tracking-wide uppercase";
      } else if (stepNum === currentStep) {
        ind.className = "msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-indigo-600 text-white shadow-md border-4 border-white dark:border-zinc-900 ring-4 ring-indigo-100 scale-110";
        ind.innerHTML = stepNum;
        label.className = "msf-step-label text-xs font-bold text-indigo-600 mt-2 transition-colors duration-300 tracking-wide uppercase";
      } else {
        ind.className = "msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-gray-200 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 dark:text-gray-500 border-4 border-white dark:border-zinc-900";
        ind.innerHTML = stepNum;
        label.className = "msf-step-label text-xs font-medium text-gray-400 dark:text-gray-500 mt-2 transition-colors duration-300 tracking-wide uppercase";
      }
    });
    
    contents.forEach((content, index) => {
      const stepNum = index + 1;
      if (stepNum === currentStep) {
        content.className = "msf-step-content absolute w-full transition-all duration-500 transform translate-x-0 opacity-100 delay-100";
      } else if (stepNum < currentStep) {
        content.className = "msf-step-content absolute w-full transition-all duration-500 transform -translate-x-12 opacity-0 pointer-events-none";
      } else {
        content.className = "msf-step-content absolute w-full transition-all duration-500 transform translate-x-12 opacity-0 pointer-events-none";
      }
    });
    
    if (currentStep === 1) {
      btnPrev.className = "px-6 py-2.5 border border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-300 font-bold rounded-xl transition-all duration-300 opacity-0 pointer-events-none absolute left-0";
    } else {
      btnPrev.className = "px-6 py-2.5 border border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-300 font-bold rounded-xl hover:bg-gray-50 dark:bg-zinc-950 hover:text-indigo-600 focus:outline-none focus:ring-2 focus:ring-gray-200 transition-all duration-300 opacity-100 flex items-center justify-center gap-2";
    }
    
    if (currentStep === steps.length) {
      btnNext.innerHTML = 'Submit <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>';
      btnNext.className = "px-6 py-2.5 bg-emerald-500 text-white font-bold rounded-xl hover:bg-emerald-600 focus:outline-none focus:ring-2 focus:ring-emerald-400 focus:ring-offset-2 transition-all duration-300 ml-auto shadow-[0_4px_14px_0_rgba(16,185,129,0.39)] hover:shadow-[0_6px_20px_rgba(16,185,129,0.23)] hover:-translate-y-0.5 flex items-center justify-center gap-2 active:translate-y-0";
    } else {
      btnNext.innerHTML = 'Next <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>';
      btnNext.className = "px-6 py-2.5 bg-indigo-600 text-white font-bold rounded-xl hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-all duration-300 ml-auto shadow-[0_4px_14px_0_rgba(79,70,229,0.39)] hover:shadow-[0_6px_20px_rgba(79,70,229,0.23)] hover:-translate-y-0.5 flex items-center justify-center gap-2 active:translate-y-0";
    }
  }

  btnPrev.addEventListener('click', () => {
    if (currentStep > 1) {
      currentStep--;
      updateUI();
    }
  });
  
  btnNext.addEventListener('click', () => {
    if (!validateStep()) return;
    
    if (currentStep < steps.length) {
      currentStep++;
      updateUI();
    } else {
      btnNext.innerHTML = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Submitting...';
      setTimeout(() => {
        alert('Form submitted successfully!');
        currentStep = 1;
        updateUI();
      }, 1500);
    }
  });
  
  updateUI();
})();
</script>

Prise en charge du navigateur

Fonctionne bien avec les dernières versions des navigateurs modernes (Chrome, Safari, Firefox, Edge).Les classes utilitaires CSS Transitions et Tailwind sont utilisées pour l’animation, et JavaScript effectue la gestion de l’état (uniquement le remplacement des classes et le calcul de la largeur de la barre de progression), ce qui le rend léger et rapide.