contact-forms
intermediate
forms
validation
animations
input
components
ui
Categoría · Formularios Nivel de Dificultad · Intermedio Publicado el · 15 de enero de 2024

Componentes de Formularios Interactivos

Una colección completa de componentes de formularios interactivos modernos con validación en tiempo real, animaciones suaves y experiencia de usuario mejorada

#forms #validation #animations #input #components #ui

Diseño Responsivo

Soporte para Modo Oscuro

No

líneas

134

Compatibilidad del Navegador

No

Vista Previa en Vivo

Interactúa con el componente sin salir de la página.

600px

Componentes de Formularios Interactivos

Una colección completa de componentes de formularios modernos e interactivos con validación en tiempo real, animaciones suaves, características de accesibilidad y experiencia de usuario mejorada para aplicaciones web.

Características

  • Múltiples Tipos de Input: Texto, email, contraseña, select, textarea, carga de archivos y más
  • Validación en Tiempo Real: Retroalimentación instantánea con reglas de validación personalizadas
  • Animaciones Suaves: Transiciones y micro-interacciones con CSS
  • Accesibilidad: Navegación completa por teclado y soporte para lectores de pantalla
  • Diseño Responsivo: Se adapta a todos los tamaños de pantalla y dispositivos
  • Estilo Personalizable: Opciones fáciles de tematización y personalización
  • Manejo de Errores: Mensajes de error claros e indicadores visuales
  • Indicadores de Progreso: Seguimiento de progreso para formularios de múltiples pasos

Demo

<div class="form-container">
  
  <form class="interactive-form contact-form" id="contactForm">
    <div class="form-header">
      <h2 class="form-title">Contáctanos</h2>
      <p class="form-description">Nos encantaría saber de ti. Envíanos un mensaje y responderemos lo antes posible.</p>
    </div>

    <div class="form-grid">
      
      <div class="form-group">
        <label for="name" class="form-label">Nombre Completo</label>
        <div class="input-wrapper">
          <input type="text" id="name" name="name" class="form-input" required>
          <span class="input-focus-border"></span>
          <div class="input-icon">
            <svg viewBox="0 0 24 24" fill="currentColor">
              <path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
            </svg>
          </div>
        </div>
        <div class="form-feedback"></div>
      </div>

      
      <div class="form-group">
        <label for="email" class="form-label">Dirección de Email</label>
        <div class="input-wrapper">
          <input type="email" id="email" name="email" class="form-input" required>
          <span class="input-focus-border"></span>
          <div class="input-icon">
            <svg viewBox="0 0 24 24" fill="currentColor">
              <path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/>
            </svg>
          </div>
        </div>
        <div class="form-feedback"></div>
      </div>
    </div>

    
    <div class="form-group">
      <label for="phone" class="form-label">Número de Teléfono</label>
      <div class="input-wrapper">
        <input type="tel" id="phone" name="phone" class="form-input">
        <span class="input-focus-border"></span>
        <div class="input-icon">
          <svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"/>
          </svg>
        </div>
      </div>
      <div class="form-feedback"></div>
    </div>

    
    <div class="form-group">
      <label for="subject" class="form-label">Asunto</label>
      <div class="select-wrapper">
        <select id="subject" name="subject" class="form-select" required>
          <option value="">Elige un asunto</option>
          <option value="general">Consulta General</option>
          <option value="support">Soporte Técnico</option>
          <option value="sales">Pregunta de Ventas</option>
          <option value="feedback">Comentarios</option>
          <option value="other">Otro</option>
        </select>
        <div class="select-icon">
          <svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M7 10l5 5 5-5z"/>
          </svg>
        </div>
      </div>
      <div class="form-feedback"></div>
    </div>

    
    <div class="form-group">
      <label for="message" class="form-label">Mensaje</label>
      <div class="textarea-wrapper">
        <textarea id="message" name="message" class="form-textarea" rows="5" required placeholder="Cuéntanos más sobre tu consulta..."></textarea>
        <span class="input-focus-border"></span>
        <div class="character-count">
          <span class="current-count">0</span>/<span class="max-count">500</span>
        </div>
      </div>
      <div class="form-feedback"></div>
    </div>

    
    <div class="form-group">
      <label for="attachment" class="form-label">Adjunto (Opcional)</label>
      <div class="file-upload-wrapper">
        <input type="file" id="attachment" name="attachment" class="file-input" accept=".pdf,.doc,.docx,.jpg,.png" multiple>
        <div class="file-upload-area">
          <div class="file-upload-icon">
            <svg viewBox="0 0 24 24" fill="currentColor">
              <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
            </svg>
          </div>
          <div class="file-upload-text">
            <span class="file-upload-title">Arrastra archivos aquí o haz clic para explorar</span>
            <span class="file-upload-subtitle">PDF, DOC, JPG, PNG hasta 10MB</span>
          </div>
        </div>
        <div class="file-list"></div>
      </div>
      <div class="form-feedback"></div>
    </div>

    
    <div class="form-group">
      <div class="checkbox-wrapper">
        <input type="checkbox" id="newsletter" name="newsletter" class="form-checkbox">
        <label for="newsletter" class="checkbox-label">
          <span class="checkbox-custom">
            <svg class="checkbox-icon" viewBox="0 0 24 24" fill="currentColor">
              <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
            </svg>
          </span>
          Suscribirse a nuestro boletín para actualizaciones y noticias
        </label>
      </div>
    </div>

    
    <div class="form-group">
      <div class="checkbox-wrapper">
        <input type="checkbox" id="privacy" name="privacy" class="form-checkbox" required>
        <label for="privacy" class="checkbox-label">
          <span class="checkbox-custom">
            <svg class="checkbox-icon" viewBox="0 0 24 24" fill="currentColor">
              <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
            </svg>
          </span>
          Acepto la <a href="#" class="privacy-link">Política de Privacidad</a> y los <a href="#" class="privacy-link">Términos de Servicio</a>
        </label>
      </div>
      <div class="form-feedback"></div>
    </div>

    
    <div class="form-actions">
      <button type="submit" class="btn-submit">
        <span class="btn-text">Enviar Mensaje</span>
        <span class="btn-loading">
          <svg class="loading-spinner" viewBox="0 0 24 24">
            <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-dasharray="32" stroke-dashoffset="32">
              <animate attributeName="stroke-dasharray" dur="2s" values="0 32;16 16;0 32;0 32" repeatCount="indefinite"/>
              <animate attributeName="stroke-dashoffset" dur="2s" values="0;-16;-32;-32" repeatCount="indefinite"/>
            </circle>
          </svg>
        </span>
        <span class="btn-success">
          <svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
          </svg>
        </span>
      </button>
    </div>
  </form>

  
  <form class="interactive-form multi-step-form" id="multiStepForm">
    <div class="form-header">
      <h2 class="form-title">Crear Cuenta</h2>
      <div class="progress-bar">
        <div class="progress-step active" data-step="1">
          <div class="step-number">1</div>
          <div class="step-label">Info Personal</div>
        </div>
        <div class="progress-step" data-step="2">
          <div class="step-number">2</div>
          <div class="step-label">Detalles de Cuenta</div>
        </div>
        <div class="progress-step" data-step="3">
          <div class="step-number">3</div>
          <div class="step-label">Preferencias</div>
        </div>
        <div class="progress-step" data-step="4">
          <div class="step-number">4</div>
          <div class="step-label">Confirmación</div>
        </div>
      </div>
    </div>

    
    <div class="form-step active" data-step="1">
      <div class="form-grid">
        <div class="form-group">
          <label for="firstName" class="form-label">Nombre</label>
          <div class="input-wrapper">
            <input type="text" id="firstName" name="firstName" class="form-input" required>
            <span class="input-focus-border"></span>
          </div>
          <div class="form-feedback"></div>
        </div>
        <div class="form-group">
          <label for="lastName" class="form-label">Apellido</label>
          <div class="input-wrapper">
            <input type="text" id="lastName" name="lastName" class="form-input" required>
            <span class="input-focus-border"></span>
          </div>
          <div class="form-feedback"></div>
        </div>
      </div>
      <div class="form-group">
        <label for="birthDate" class="form-label">Fecha de Nacimiento</label>
        <div class="input-wrapper">
          <input type="date" id="birthDate" name="birthDate" class="form-input" required>
          <span class="input-focus-border"></span>
        </div>
        <div class="form-feedback"></div>
      </div>
    </div>

    
    <div class="form-step" data-step="2">
      <div class="form-group">
        <label for="username" class="form-label">Nombre de Usuario</label>
        <div class="input-wrapper">
          <input type="text" id="username" name="username" class="form-input" required>
          <span class="input-focus-border"></span>
          <div class="input-validation-icon"></div>
        </div>
        <div class="form-feedback"></div>
      </div>
      <div class="form-group">
        <label for="password" class="form-label">Contraseña</label>
        <div class="input-wrapper">
          <input type="password" id="password" name="password" class="form-input" required>
          <span class="input-focus-border"></span>
          <button type="button" class="password-toggle" aria-label="Alternar visibilidad de contraseña">
            <svg class="eye-open" viewBox="0 0 24 24" fill="currentColor">
              <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
            </svg>
            <svg class="eye-closed" viewBox="0 0 24 24" fill="currentColor" style="display: none;">
              <path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/>
            </svg>
          </button>
        </div>
        <div class="password-strength">
          <div class="strength-bar">
            <div class="strength-fill"></div>
          </div>
          <div class="strength-text">Fuerza de contraseña: <span class="strength-level">Débil</span></div>
        </div>
        <div class="form-feedback"></div>
      </div>
    </div>

    
    <div class="form-step" data-step="3">
      <div class="form-group">
        <label class="form-label">Preferencias de Notificación</label>
        <div class="radio-group">
          <div class="radio-wrapper">
            <input type="radio" id="emailNotif" name="notifications" value="email" class="form-radio">
            <label for="emailNotif" class="radio-label">
              <span class="radio-custom"></span>
              Solo notificaciones por email
            </label>
          </div>
          <div class="radio-wrapper">
            <input type="radio" id="smsNotif" name="notifications" value="sms" class="form-radio">
            <label for="smsNotif" class="radio-label">
              <span class="radio-custom"></span>
              Solo notificaciones por SMS
            </label>
          </div>
          <div class="radio-wrapper">
            <input type="radio" id="bothNotif" name="notifications" value="both" class="form-radio" checked>
            <label for="bothNotif" class="radio-label">
              <span class="radio-custom"></span>
              Ambos email y SMS
            </label>
          </div>
        </div>
      </div>
    </div>

    
    <div class="form-step" data-step="4">
      <div class="confirmation-content">
        <div class="success-icon">
          <svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
          </svg>
        </div>
        <h3>¡Cuenta Creada Exitosamente!</h3>
        <p>Bienvenido a nuestra plataforma. Tu cuenta ha sido creada y ahora puedes comenzar a usar nuestros servicios.</p>
      </div>
    </div>

    
    <div class="form-navigation">
      <button type="button" class="btn-secondary btn-prev" disabled>
        <svg viewBox="0 0 24 24" fill="currentColor">
          <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
        </svg>
        Anterior
      </button>
      <button type="button" class="btn-primary btn-next">
        Siguiente
        <svg viewBox="0 0 24 24" fill="currentColor">
          <path d="M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z"/>
        </svg>
      </button>
      <button type="submit" class="btn-primary btn-submit" style="display: none;">
        Crear Cuenta
      </button>
    </div>
  </form>
</div>
.form-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
  display: flex;
  flex-direction: column;
  gap: 3rem;
}.interactive-form {
  background: white;
  border-radius: 16px;
  padding: 2rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
  border: 1px solid #e5e7eb;
}

.form-header {
  text-align: center;
  margin-bottom: 2rem;
}

.form-title {
  font-size: 2rem;
  font-weight: 700;
  color: #1a1a1a;
  margin: 0 0 0.5rem 0;
}

.form-description {
  color: #6b7280;
  font-size: 1rem;
  margin: 0;
  line-height: 1.6;
}.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
}.form-group {
  margin-bottom: 1.5rem;
}

.form-label {
  display: block;
  font-weight: 600;
  color: #374151;
  margin-bottom: 0.5rem;
  font-size: 0.875rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}.input-wrapper {
  position: relative;
}

.form-input {
  width: 100%;
  padding: 1rem 1rem 1rem 3rem;
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  font-size: 1rem;
  transition: all 0.3s ease;
  background: white;
  outline: none;
}

.form-input:focus {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.form-input:valid {
  border-color: #10b981;
}

.form-input:invalid:not(:placeholder-shown) {
  border-color: #ef4444;
}

.input-focus-border {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0;
  height: 2px;
  background: linear-gradient(90deg, #3b82f6, #1d4ed8);
  transition: width 0.3s ease;
}

.form-input:focus + .input-focus-border {
  width: 100%;
}

.input-icon {
  position: absolute;
  left: 1rem;
  top: 50%;
  transform: translateY(-50%);
  color: #9ca3af;
  transition: color 0.3s ease;
}

.input-icon svg {
  width: 20px;
  height: 20px;
}

.form-input:focus ~ .input-icon {
  color: #3b82f6;
}.select-wrapper {
  position: relative;
}

.form-select {
  width: 100%;
  padding: 1rem 3rem 1rem 1rem;
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  font-size: 1rem;
  background: white;
  cursor: pointer;
  outline: none;
  appearance: none;
  transition: all 0.3s ease;
}

.form-select:focus {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.select-icon {
  position: absolute;
  right: 1rem;
  top: 50%;
  transform: translateY(-50%);
  color: #9ca3af;
  pointer-events: none;
  transition: transform 0.3s ease;
}

.select-icon svg {
  width: 20px;
  height: 20px;
}

.form-select:focus ~ .select-icon {
  transform: translateY(-50%) rotate(180deg);
  color: #3b82f6;
}.textarea-wrapper {
  position: relative;
}

.form-textarea {
  width: 100%;
  padding: 1rem;
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  font-size: 1rem;
  font-family: inherit;
  resize: vertical;
  min-height: 120px;
  outline: none;
  transition: all 0.3s ease;
}

.form-textarea:focus {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.character-count {
  position: absolute;
  bottom: 0.5rem;
  right: 1rem;
  font-size: 0.75rem;
  color: #9ca3af;
  background: white;
  padding: 0.25rem;
}.file-upload-wrapper {
  position: relative;
}

.file-input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  cursor: pointer;
}

.file-upload-area {
  border: 2px dashed #d1d5db;
  border-radius: 8px;
  padding: 2rem;
  text-align: center;
  transition: all 0.3s ease;
  cursor: pointer;
}

.file-upload-area:hover,
.file-upload-area.dragover {
  border-color: #3b82f6;
  background: rgba(59, 130, 246, 0.05);
}

.file-upload-icon {
  width: 48px;
  height: 48px;
  margin: 0 auto 1rem;
  color: #9ca3af;
}

.file-upload-icon svg {
  width: 100%;
  height: 100%;
}

.file-upload-title {
  display: block;
  font-weight: 600;
  color: #374151;
  margin-bottom: 0.25rem;
}

.file-upload-subtitle {
  display: block;
  font-size: 0.875rem;
  color: #9ca3af;
}

.file-list {
  margin-top: 1rem;
}

.file-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem;
  background: #f9fafb;
  border-radius: 6px;
  margin-bottom: 0.5rem;
}

.file-info {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.file-icon {
  width: 24px;
  height: 24px;
  color: #6b7280;
}

.file-details {
  display: flex;
  flex-direction: column;
}

.file-name {
  font-weight: 500;
  color: #374151;
  font-size: 0.875rem;
}

.file-size {
  font-size: 0.75rem;
  color: #9ca3af;
}

.file-remove {
  background: none;
  border: none;
  color: #ef4444;
  cursor: pointer;
  padding: 0.25rem;
  border-radius: 4px;
  transition: background 0.3s ease;
}

.file-remove:hover {
  background: rgba(239, 68, 68, 0.1);
}.checkbox-wrapper {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}

.form-checkbox {
  position: absolute;
  opacity: 0;
  cursor: pointer;
}

.checkbox-custom {
  width: 20px;
  height: 20px;
  border: 2px solid #d1d5db;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s ease;
  flex-shrink: 0;
  margin-top: 0.125rem;
}

.checkbox-icon {
  width: 14px;
  height: 14px;
  color: white;
  opacity: 0;
  transform: scale(0.5);
  transition: all 0.3s ease;
}

.form-checkbox:checked + .checkbox-label .checkbox-custom {
  background: #3b82f6;
  border-color: #3b82f6;
}

.form-checkbox:checked + .checkbox-label .checkbox-icon {
  opacity: 1;
  transform: scale(1);
}

.checkbox-label {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  cursor: pointer;
  font-size: 0.875rem;
  line-height: 1.5;
  color: #374151;
}

.privacy-link {
  color: #3b82f6;
  text-decoration: none;
  font-weight: 500;
}

.privacy-link:hover {
  text-decoration: underline;
}.radio-group {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.radio-wrapper {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.form-radio {
  position: absolute;
  opacity: 0;
  cursor: pointer;
}

.radio-custom {
  width: 20px;
  height: 20px;
  border: 2px solid #d1d5db;
  border-radius: 50%;
  position: relative;
  transition: all 0.3s ease;
  flex-shrink: 0;
}

.radio-custom::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0);
  width: 8px;
  height: 8px;
  background: #3b82f6;
  border-radius: 50%;
  transition: transform 0.3s ease;
}

.form-radio:checked + .radio-label .radio-custom {
  border-color: #3b82f6;
}

.form-radio:checked + .radio-label .radio-custom::after {
  transform: translate(-50%, -50%) scale(1);
}

.radio-label {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  cursor: pointer;
  font-size: 0.875rem;
  color: #374151;
}.password-toggle {
  position: absolute;
  right: 1rem;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  color: #9ca3af;
  cursor: pointer;
  padding: 0.25rem;
  border-radius: 4px;
  transition: color 0.3s ease;
}

.password-toggle:hover {
  color: #6b7280;
}

.password-toggle svg {
  width: 20px;
  height: 20px;
}.password-strength {
  margin-top: 0.5rem;
}

.strength-bar {
  width: 100%;
  height: 4px;
  background: #e5e7eb;
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: 0.5rem;
}

.strength-fill {
  height: 100%;
  width: 0;
  transition: all 0.3s ease;
  border-radius: 2px;
}

.strength-fill.weak {
  width: 25%;
  background: #ef4444;
}

.strength-fill.fair {
  width: 50%;
  background: #f59e0b;
}

.strength-fill.good {
  width: 75%;
  background: #3b82f6;
}

.strength-fill.strong {
  width: 100%;
  background: #10b981;
}

.strength-text {
  font-size: 0.75rem;
  color: #6b7280;
}

.strength-level {
  font-weight: 600;
}.form-feedback {
  margin-top: 0.5rem;
  font-size: 0.875rem;
  min-height: 1.25rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.form-feedback.error {
  color: #ef4444;
}

.form-feedback.success {
  color: #10b981;
}

.form-feedback.warning {
  color: #f59e0b;
}

.feedback-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}.form-actions {
  margin-top: 2rem;
}

.btn-submit {
  width: 100%;
  padding: 1rem 2rem;
  background: linear-gradient(135deg, #3b82f6, #1d4ed8);
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
}

.btn-submit:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 8px 20px rgba(59, 130, 246, 0.3);
}

.btn-submit:disabled {
  opacity: 0.7;
  cursor: not-allowed;
  transform: none;
}

.btn-text,
.btn-loading,
.btn-success {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  transition: all 0.3s ease;
}

.btn-loading,
.btn-success {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  opacity: 0;
  transform: translateY(100%);
}

.btn-submit.loading .btn-text {
  opacity: 0;
  transform: translateY(-100%);
}

.btn-submit.loading .btn-loading {
  opacity: 1;
  transform: translateY(0);
}

.btn-submit.success .btn-text,
.btn-submit.success .btn-loading {
  opacity: 0;
  transform: translateY(-100%);
}

.btn-submit.success .btn-success {
  opacity: 1;
  transform: translateY(0);
}

.loading-spinner {
  width: 20px;
  height: 20px;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}.progress-bar {
  display: flex;
  justify-content: space-between;
  margin: 2rem 0;
  position: relative;
}

.progress-bar::before {
  content: '';
  position: absolute;
  top: 20px;
  left: 0;
  right: 0;
  height: 2px;
  background: #e5e7eb;
  z-index: 1;
}

.progress-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  z-index: 2;
}

.step-number {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #e5e7eb;
  color: #9ca3af;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  transition: all 0.3s ease;
  margin-bottom: 0.5rem;
}

.progress-step.active .step-number {
  background: #3b82f6;
  color: white;
}

.progress-step.completed .step-number {
  background: #10b981;
  color: white;
}

.step-label {
  font-size: 0.75rem;
  color: #6b7280;
  text-align: center;
  font-weight: 500;
}

.progress-step.active .step-label {
  color: #3b82f6;
  font-weight: 600;
}

.form-step {
  display: none;
  animation: fadeInSlide 0.3s ease;
}

.form-step.active {
  display: block;
}

@keyframes fadeInSlide {
  from {
    opacity: 0;
    transform: translateX(20px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

.form-navigation {
  display: flex;
  justify-content: space-between;
  margin-top: 2rem;
  gap: 1rem;
}

.btn-secondary {
  padding: 0.75rem 1.5rem;
  background: #f3f4f6;
  color: #374151;
  border: 1px solid #d1d5db;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.btn-secondary:hover:not(:disabled) {
  background: #e5e7eb;
}

.btn-secondary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-primary {
  padding: 0.75rem 1.5rem;
  background: linear-gradient(135deg, #3b82f6, #1d4ed8);
  color: white;
  border: none;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.btn-primary:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}

.btn-primary svg {
  width: 16px;
  height: 16px;
}.confirmation-content {
  text-align: center;
  padding: 2rem 0;
}

.success-icon {
  width: 80px;
  height: 80px;
  background: #10b981;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 1.5rem;
  color: white;
}

.success-icon svg {
  width: 40px;
  height: 40px;
}

.confirmation-content h3 {
  font-size: 1.5rem;
  font-weight: 700;
  color: #1a1a1a;
  margin: 0 0 1rem 0;
}

.confirmation-content p {
  color: #6b7280;
  line-height: 1.6;
  margin: 0;
}.dark .interactive-form {
  background: #1f2937;
  border-color: #374151;
}

.dark .form-title {
  color: white;
}

.dark .form-description {
  color: #d1d5db;
}

.dark .form-label {
  color: #e5e7eb;
}

.dark .form-input,
.dark .form-select,
.dark .form-textarea {
  background: #374151;
  border-color: #4b5563;
  color: white;
}

.dark .form-input:focus,
.dark .form-select:focus,
.dark .form-textarea:focus {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.dark .file-upload-area {
  border-color: #4b5563;
  background: #374151;
}

.dark .file-upload-area:hover {
  border-color: #3b82f6;
  background: rgba(59, 130, 246, 0.1);
}

.dark .checkbox-custom,
.dark .radio-custom {
  border-color: #4b5563;
  background: #374151;
}

.dark .step-number {
  background: #374151;
  color: #d1d5db;
}@media (max-width: 768px) {
  .form-container {
    padding: 1rem;
  }
  
  .interactive-form {
    padding: 1.5rem;
  }
  
  .form-grid {
    grid-template-columns: 1fr;
    gap: 1rem;
  }
  
  .form-title {
    font-size: 1.5rem;
  }
  
  .progress-bar {
    flex-wrap: wrap;
    gap: 1rem;
  }
  
  .step-label {
    display: none;
  }
  
  .form-navigation {
    flex-direction: column;
  }
}

@media (max-width: 480px) {
  .form-input {
    padding: 0.875rem 0.875rem 0.875rem 2.5rem;
  }
  
  .input-icon {
    left: 0.75rem;
  }
  
  .input-icon svg {
    width: 18px;
    height: 18px;
  }
  
  .btn-submit {
    padding: 0.875rem 1.5rem;
  }
}
class FormulariosInteractivos {
  constructor() {
    this.formularios = document.querySelectorAll('.interactive-form');
    this.reglasValidacion = {
      email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
      telefono: /^[\+]?[1-9][\d]{0,2}[\s\-]?[\(]?[\d]{1,3}[\)]?[\s\-]?[\d]{3,4}[\s\-]?[\d]{3,4}$/,
      contrasena: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
    };
    
    this.init();
  }

  init() {
    this.formularios.forEach(formulario => {
      this.configurarValidacionFormulario(formulario);
      this.configurarCargaArchivos(formulario);
      this.configurarAlternarContrasena(formulario);
      this.configurarContadorCaracteres(formulario);
      this.configurarMultiPaso(formulario);
    });
  }

  configurarValidacionFormulario(formulario) {
    const inputs = formulario.querySelectorAll('.form-input, .form-select, .form-textarea');
    
    inputs.forEach(input => {

      input.addEventListener('input', () => {
        this.validarCampo(input);
      });
      
      input.addEventListener('blur', () => {
        this.validarCampo(input);
      });

      if (input.name === 'username') {
        let temporizadorDebounce;
        input.addEventListener('input', () => {
          clearTimeout(temporizadorDebounce);
          temporizadorDebounce = setTimeout(() => {
            this.verificarDisponibilidadUsuario(input);
          }, 500);
        });
      }

      if (input.type === 'password') {
        input.addEventListener('input', () => {
          this.actualizarFuerzaContrasena(input);
        });
      }
    });

    formulario.addEventListener('submit', (e) => {
      e.preventDefault();
      this.manejarEnvioFormulario(formulario);
    });
  }

  validarCampo(campo) {
    const valor = campo.value.trim();
    const nombreCampo = campo.name;
    const retroalimentacion = campo.closest('.form-group').querySelector('.form-feedback');
    
    let esValido = true;
    let mensaje = '';
    let tipo = 'success';

    if (campo.hasAttribute('required') && !valor) {
      esValido = false;
      mensaje = 'Este campo es requerido';
      tipo = 'error';
    }

    else if (campo.type === 'email' && valor && !this.reglasValidacion.email.test(valor)) {
      esValido = false;
      mensaje = 'Por favor ingresa una dirección de email válida';
      tipo = 'error';
    }

    else if (campo.type === 'tel' && valor && !this.reglasValidacion.telefono.test(valor)) {
      esValido = false;
      mensaje = 'Por favor ingresa un número de teléfono válido';
      tipo = 'error';
    }

    else if (campo.type === 'password' && valor && !this.reglasValidacion.contrasena.test(valor)) {
      esValido = false;
      mensaje = 'La contraseña debe tener al menos 8 caracteres con mayúscula, minúscula, número y carácter especial';
      tipo = 'error';
    }

    else if (valor && campo.checkValidity()) {
      mensaje = '¡Se ve bien!';
      tipo = 'success';
    }
    
    this.mostrarRetroalimentacionCampo(retroalimentacion, mensaje, tipo);

    campo.classList.toggle('valid', esValido && valor);
    campo.classList.toggle('invalid', !esValido && valor);
    
    return esValido;
  }

  mostrarRetroalimentacionCampo(elementoRetroalimentacion, mensaje, tipo) {
    elementoRetroalimentacion.className = `form-feedback ${tipo}`;
    
    const icono = this.obtenerIconoValidacion(tipo);
    elementoRetroalimentacion.innerHTML = mensaje ? `${icono}<span>${mensaje}</span>` : '';
  }

  obtenerIconoValidacion(tipo) {
    const iconos = {
      success: '<svg class="feedback-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>',
      error: '<svg class="feedback-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>',
      warning: '<svg class="feedback-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>'
    };
    return iconos[tipo] || '';
  }

  async verificarDisponibilidadUsuario(input) {
    const nombreUsuario = input.value.trim();
    if (nombreUsuario.length < 3) return;
    
    const wrapper = input.closest('.input-wrapper');
    const iconoValidacion = wrapper.querySelector('.input-validation-icon');
    
    if (!iconoValidacion) {
      const icono = document.createElement('div');
      icono.className = 'input-validation-icon';
      wrapper.appendChild(icono);
    }

    try {
      const estaDisponible = await this.simularVerificacionUsuario(nombreUsuario);
      const retroalimentacion = input.closest('.form-group').querySelector('.form-feedback');
      
      if (estaDisponible) {
        this.mostrarRetroalimentacionCampo(retroalimentacion, 'Nombre de usuario disponible', 'success');
        input.classList.add('valid');
        input.classList.remove('invalid');
      } else {
        this.mostrarRetroalimentacionCampo(retroalimentacion, 'Nombre de usuario ya está en uso', 'error');
        input.classList.add('invalid');
        input.classList.remove('valid');
      }
    } catch (error) {
      console.error('Verificación de usuario falló:', error);
    }
  }

  simularVerificacionUsuario(nombreUsuario) {
    return new Promise((resolve) => {
      setTimeout(() => {

        const usuariosOcupados = ['admin', 'usuario', 'test', 'demo'];
        resolve(!usuariosOcupados.includes(nombreUsuario.toLowerCase()));
      }, 1000);
    });
  }

  actualizarFuerzaContrasena(input) {
    const contrasena = input.value;
    const barraFuerza = input.closest('.form-group').querySelector('.strength-fill');
    const textoFuerza = input.closest('.form-group').querySelector('.strength-level');
    
    if (!barraFuerza || !textoFuerza) return;
    
    const fuerza = this.calcularFuerzaContrasena(contrasena);
    
    barraFuerza.className = `strength-fill ${fuerza.nivel}`;
    textoFuerza.textContent = fuerza.texto;
  }

  calcularFuerzaContrasena(contrasena) {
    let puntuacion = 0;
    
    if (contrasena.length >= 8) puntuacion++;
    if (/[a-z]/.test(contrasena)) puntuacion++;
    if (/[A-Z]/.test(contrasena)) puntuacion++;
    if (/\d/.test(contrasena)) puntuacion++;
    if (/[@$!%*?&]/.test(contrasena)) puntuacion++;
    
    const niveles = {
      0: { nivel: 'weak', texto: 'Muy Débil' },
      1: { nivel: 'weak', texto: 'Débil' },
      2: { nivel: 'fair', texto: 'Regular' },
      3: { nivel: 'good', texto: 'Buena' },
      4: { nivel: 'strong', texto: 'Fuerte' },
      5: { nivel: 'strong', texto: 'Muy Fuerte' }
    };
    
    return niveles[puntuacion] || niveles[0];
  }

  configurarCargaArchivos(formulario) {
    const inputsArchivo = formulario.querySelectorAll('.file-input');
    
    inputsArchivo.forEach(input => {
      const areaSubida = input.closest('.file-upload-wrapper').querySelector('.file-upload-area');
      const listaArchivos = input.closest('.file-upload-wrapper').querySelector('.file-list');

      areaSubida.addEventListener('dragover', (e) => {
        e.preventDefault();
        areaSubida.classList.add('dragover');
      });
      
      areaSubida.addEventListener('dragleave', () => {
        areaSubida.classList.remove('dragover');
      });
      
      areaSubida.addEventListener('drop', (e) => {
        e.preventDefault();
        areaSubida.classList.remove('dragover');
        
        const archivos = Array.from(e.dataTransfer.files);
        this.manejarSeleccionArchivos(archivos, listaArchivos);
      });

      input.addEventListener('change', (e) => {
        const archivos = Array.from(e.target.files);
        this.manejarSeleccionArchivos(archivos, listaArchivos);
      });
    });
  }

  manejarSeleccionArchivos(archivos, listaArchivos) {
    archivos.forEach(archivo => {
      if (this.validarArchivo(archivo)) {
        this.agregarArchivoALista(archivo, listaArchivos);
      }
    });
  }

  validarArchivo(archivo) {
    const tamanoMaximo = 10 * 1024 * 1024; // 10MB
    const tiposPermitidos = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'image/jpeg', 'image/png'];
    
    if (archivo.size > tamanoMaximo) {
      alert(`El archivo ${archivo.name} es muy grande. El tamaño máximo es 10MB.`);
      return false;
    }
    
    if (!tiposPermitidos.includes(archivo.type)) {
      alert(`El archivo ${archivo.name} no es un formato soportado.`);
      return false;
    }
    
    return true;
  }

  agregarArchivoALista(archivo, listaArchivos) {
    const elementoArchivo = document.createElement('div');
    elementoArchivo.className = 'file-item';
    
    elementoArchivo.innerHTML = `
      <div class="file-info">
        <div class="file-icon">
          <svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
          </svg>
        </div>
        <div class="file-details">
          <div class="file-name">${archivo.name}</div>
          <div class="file-size">${this.formatearTamanoArchivo(archivo.size)}</div>
        </div>
      </div>
      <button type="button" class="file-remove" aria-label="Remover archivo">
        <svg viewBox="0 0 24 24" fill="currentColor">
          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
        </svg>
      </button>
    `;
    
    listaArchivos.appendChild(elementoArchivo);

    elementoArchivo.querySelector('.file-remove').addEventListener('click', () => {
      elementoArchivo.remove();
    });
  }

  formatearTamanoArchivo(bytes) {
    if (bytes === 0) return '0 Bytes';
    
    const k = 1024;
    const tamaños = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + tamaños[i];
  }

  configurarAlternarContrasena(formulario) {
    const botonesAlternar = formulario.querySelectorAll('.password-toggle');
    
    botonesAlternar.forEach(boton => {
      boton.addEventListener('click', () => {
        const input = boton.closest('.input-wrapper').querySelector('input');
        const iconoAbierto = boton.querySelector('.eye-open');
        const iconoCerrado = boton.querySelector('.eye-closed');
        
        if (input.type === 'password') {
          input.type = 'text';
          iconoAbierto.style.display = 'none';
          iconoCerrado.style.display = 'block';
        } else {
          input.type = 'password';
          iconoAbierto.style.display = 'block';
          iconoCerrado.style.display = 'none';
        }
      });
    });
  }

  configurarContadorCaracteres(formulario) {
    const textareas = formulario.querySelectorAll('.form-textarea');
    
    textareas.forEach(textarea => {
      const contadorActual = textarea.closest('.textarea-wrapper').querySelector('.current-count');
      const contadorMaximo = textarea.closest('.textarea-wrapper').querySelector('.max-count');
      
      if (contadorActual && contadorMaximo) {
        const limite = parseInt(contadorMaximo.textContent);
        
        textarea.addEventListener('input', () => {
          const longitudActual = textarea.value.length;
          contadorActual.textContent = longitudActual;

          const porcentaje = (longitudActual / limite) * 100;
          if (porcentaje >= 90) {
            contadorActual.style.color = '#ef4444';
          } else if (porcentaje >= 75) {
            contadorActual.style.color = '#f59e0b';
          } else {
            contadorActual.style.color = '#9ca3af';
          }

          if (longitudActual > limite) {
            textarea.value = textarea.value.substring(0, limite);
            contadorActual.textContent = limite;
          }
        });
      }
    });
  }

  configurarMultiPaso(formulario) {
    if (!formulario.classList.contains('multi-step-form')) return;
    
    const pasos = formulario.querySelectorAll('.form-step');
    const pasosProgreso = formulario.querySelectorAll('.progress-step');
    const botonAnterior = formulario.querySelector('.btn-prev');
    const botonSiguiente = formulario.querySelector('.btn-next');
    const botonEnviar = formulario.querySelector('.btn-submit');
    
    let pasoActual = 1;

    botonSiguiente.addEventListener('click', () => {
      if (this.validarPasoActual(formulario, pasoActual)) {
        this.irAlPaso(pasoActual + 1, pasos, pasosProgreso, botonAnterior, botonSiguiente, botonEnviar);
        pasoActual++;
      }
    });
    
    botonAnterior.addEventListener('click', () => {
      this.irAlPaso(pasoActual - 1, pasos, pasosProgreso, botonAnterior, botonSiguiente, botonEnviar);
      pasoActual--;
    });
  }

  validarPasoActual(formulario, numeroPaso) {
    const pasoActual = formulario.querySelector(`[data-step="${numeroPaso}"]`);
    const camposRequeridos = pasoActual.querySelectorAll('[required]');
    
    let esValido = true;
    
    camposRequeridos.forEach(campo => {
      if (!this.validarCampo(campo)) {
        esValido = false;
      }
    });
    
    return esValido;
  }

  irAlPaso(numeroPaso, pasos, pasosProgreso, botonAnterior, botonSiguiente, botonEnviar) {

    pasos.forEach((paso, indice) => {
      paso.classList.toggle('active', indice + 1 === numeroPaso);
    });

    pasosProgreso.forEach((paso, indice) => {
      const numeroEtapa = indice + 1;
      paso.classList.toggle('active', numeroEtapa === numeroPaso);
      paso.classList.toggle('completed', numeroEtapa < numeroPaso);
    });

    botonAnterior.disabled = numeroPaso === 1;
    
    if (numeroPaso === pasos.length) {
      botonSiguiente.style.display = 'none';
      botonEnviar.style.display = 'flex';
    } else {
      botonSiguiente.style.display = 'flex';
      botonEnviar.style.display = 'none';
    }
  }

  async manejarEnvioFormulario(formulario) {
    const botonEnviar = formulario.querySelector('.btn-submit');
    const textoBoton = botonEnviar.querySelector('.btn-text');
    const cargandoBoton = botonEnviar.querySelector('.btn-loading');
    const exitoBoton = botonEnviar.querySelector('.btn-success');

    const camposRequeridos = formulario.querySelectorAll('[required]');
    let formularioValido = true;
    
    camposRequeridos.forEach(campo => {
      if (!this.validarCampo(campo)) {
        formularioValido = false;
      }
    });
    
    if (!formularioValido) {
      this.mostrarMensajeFormulario(formulario, 'Por favor corrige los errores antes de enviar', 'error');
      return;
    }

    botonEnviar.disabled = true;
    botonEnviar.classList.add('loading');
    
    try {

      const datosFormulario = this.obtenerDatosFormulario(formulario);
      await this.simularEnvioAPI(datosFormulario);

      botonEnviar.classList.remove('loading');
      botonEnviar.classList.add('success');
      
      this.mostrarMensajeFormulario(formulario, '¡Formulario enviado exitosamente!', 'success');

      setTimeout(() => {
        this.resetearFormulario(formulario);
      }, 2000);
      
    } catch (error) {

      botonEnviar.classList.remove('loading');
      botonEnviar.disabled = false;
      
      this.mostrarMensajeFormulario(formulario, 'Error al enviar el formulario. Por favor intenta de nuevo.', 'error');
      console.error('Error de envío:', error);
    }
  }

  mostrarMensajeFormulario(formulario, mensaje, tipo) {
    let contenedorMensaje = formulario.querySelector('.form-message');
    
    if (!contenedorMensaje) {
      contenedorMensaje = document.createElement('div');
      contenedorMensaje.className = 'form-message';
      formulario.insertBefore(contenedorMensaje, formulario.querySelector('.form-actions'));
    }
    
    contenedorMensaje.className = `form-message ${tipo}`;
    contenedorMensaje.innerHTML = `
      <div class="message-content">
        ${this.obtenerIconoValidacion(tipo)}
        <span>${mensaje}</span>
      </div>
    `;

    setTimeout(() => {
      contenedorMensaje.remove();
    }, 5000);
  }

  resetearFormulario(formulario) {
    formulario.reset();

    const campos = formulario.querySelectorAll('.form-input, .form-select, .form-textarea');
    campos.forEach(campo => {
      campo.classList.remove('valid', 'invalid');
    });

    const retroalimentaciones = formulario.querySelectorAll('.form-feedback');
    retroalimentaciones.forEach(retroalimentacion => {
      retroalimentacion.innerHTML = '';
      retroalimentacion.className = 'form-feedback';
    });

    const botonEnviar = formulario.querySelector('.btn-submit');
    botonEnviar.disabled = false;
    botonEnviar.classList.remove('loading', 'success');

    if (formulario.classList.contains('multi-step-form')) {
      this.resetearMultiPaso(formulario);
    }
  }

  resetearMultiPaso(formulario) {
    const pasos = formulario.querySelectorAll('.form-step');
    const pasosProgreso = formulario.querySelectorAll('.progress-step');
    const botonAnterior = formulario.querySelector('.btn-prev');
    const botonSiguiente = formulario.querySelector('.btn-next');
    const botonEnviar = formulario.querySelector('.btn-submit');
    
    this.irAlPaso(1, pasos, pasosProgreso, botonAnterior, botonSiguiente, botonEnviar);
  }

  simularEnvioAPI(datos) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {

        if (Math.random() > 0.1) {
          resolve({ exito: true, mensaje: 'Formulario enviado exitosamente' });
        } else {
          reject(new Error('Error del servidor'));
        }
      }, 2000);
    });
  }

  validarFormulario(idFormulario) {
    const formulario = document.getElementById(idFormulario);
    if (!formulario) return false;
    
    const camposRequeridos = formulario.querySelectorAll('[required]');
    let esValido = true;
    
    camposRequeridos.forEach(campo => {
      if (!this.validarCampo(campo)) {
        esValido = false;
      }
    });
    
    return esValido;
  }

  obtenerDatosFormulario(formulario) {
    const datosFormulario = new FormData(formulario);
    const datos = {};
    
    for (let [clave, valor] of datosFormulario.entries()) {
      datos[clave] = valor;
    }
    
    return datos;
  }

  establecerDatosFormulario(idFormulario, datos) {
    const formulario = document.getElementById(idFormulario);
    if (!formulario) return;
    
    Object.keys(datos).forEach(clave => {
      const campo = formulario.querySelector(`[name="${clave}"]`);
      if (campo) {
        campo.value = datos[clave];
        this.validarCampo(campo);
      }
    });
  }
}

document.addEventListener('DOMContentLoaded', () => {
  new FormulariosInteractivos();
});

Ejemplos de Uso

Formulario de Contacto Básico

<form class="interactive-form" id="contactForm">
  <div class="form-group">
    <label for="name" class="form-label">Nombre</label>
    <div class="input-wrapper">
      <input type="text" id="name" name="name" class="form-input" required>
      <span class="input-focus-border"></span>
    </div>
    <div class="form-feedback"></div>
  </div>
  
  <div class="form-group">
    <label for="email" class="form-label">Email</label>
    <div class="input-wrapper">
      <input type="email" id="email" name="email" class="form-input" required>
      <span class="input-focus-border"></span>
    </div>
    <div class="form-feedback"></div>
  </div>
  
  <button type="submit" class="btn-submit">
    <span class="btn-text">Enviar</span>
    <span class="btn-loading">Enviando...</span>
    <span class="btn-success">¡Enviado!</span>
  </button>
</form>

Registro Multi-paso

<form class="interactive-form multi-step-form" id="registrationForm">
  <div class="progress-bar">
    <div class="progress-step active" data-step="1">
      <div class="step-number">1</div>
      <div class="step-label">Personal</div>
    </div>
    <div class="progress-step" data-step="2">
      <div class="step-number">2</div>
      <div class="step-label">Cuenta</div>
    </div>
  </div>
  
  <div class="form-step active" data-step="1">
    
  </div>
  
  <div class="form-step" data-step="2">
    
  </div>
  
  <div class="form-navigation">
    <button type="button" class="btn-prev">Anterior</button>
    <button type="button" class="btn-next">Siguiente</button>
    <button type="submit" class="btn-submit" style="display: none;">Registrarse</button>
  </div>
</form>

Integración con JavaScript


const formularios = new FormulariosInteractivos();
const esValido = formularios.validarFormulario('contactForm');

const formulario = document.getElementById('contactForm');
const datos = formularios.obtenerDatosFormulario(formulario);

formularios.establecerDatosFormulario('contactForm', {
  name: 'Juan Pérez',
  email: 'juan@ejemplo.com'
});

Referencia de API

Métodos

  • validarFormulario(idFormulario) - Valida todos los campos requeridos
  • obtenerDatosFormulario(formulario) - Obtiene todos los datos del formulario
  • establecerDatosFormulario(idFormulario, datos) - Establece valores en el formulario
  • resetearFormulario(formulario) - Resetea el formulario a su estado inicial

Eventos

  • input - Validación en tiempo real mientras se escribe
  • blur - Validación cuando el campo pierde el foco
  • submit - Manejo del envío del formulario
  • change - Para selects y checkboxes

Opciones de Personalización

Variables CSS

:root {
  --form-primary-color: #3b82f6;
  --form-success-color: #10b981;
  --form-error-color: #ef4444;
  --form-warning-color: #f59e0b;
  --form-border-radius: 8px;
  --form-spacing: 1rem;
}

Reglas de Validación Personalizadas

const formularios = new FormulariosInteractivos();
formularios.reglasValidacion.telefono = /^\+?[1-9]\d{1,14}$/;

Accesibilidad

  • Navegación por Teclado: Soporte completo para Tab, Enter y teclas de flecha
  • Lectores de Pantalla: Etiquetas ARIA y roles semánticos
  • Alto Contraste: Colores que cumplen con WCAG 2.1 AA
  • Indicadores de Foco: Indicadores visuales claros para elementos enfocados

Soporte de Navegadores

  • Chrome 60+
  • Firefox 55+
  • Safari 12+
  • Edge 79+

Consideraciones de Rendimiento

  • Validación Debounced: Evita validaciones excesivas durante la escritura
  • Carga Lazy: Los archivos se procesan solo cuando se seleccionan
  • Animaciones Optimizadas: Usa transform y opacity para animaciones suaves
  • Tamaño Mínimo: CSS y JS optimizados para carga rápida

Ejemplos de Integración

React

x
import { useEffect } from 'react';

function FormularioContacto() {
  useEffect(() => {
    new FormulariosInteractivos();
  }, []);
  
  return (
    <form className="interactive-form" id="contactForm">
      
    </form>
  );
}

Vue

<template>
  <form class="interactive-form" id="contactForm">
    
  </form>
</template>

<script>
export default {
  mounted() {
    new FormulariosInteractivos();
  }
}
</script>

HTML

23

líneas

CSS

111

líneas


                <div class="form-container">
  <form class="interactive-form">
    <div class="form-group">
      <input type="text" id="name" required>
      <label for="name">Nombre completo</label>
      <span class="form-line"></span>
    </div>
    <div class="form-group">
      <input type="email" id="email" required>
      <label for="email">Correo electrónico</label>
      <span class="form-line"></span>
    </div>
    <div class="form-group">
      <textarea id="message" required></textarea>
      <label for="message">Mensaje</label>
      <span class="form-line"></span>
    </div>
    <button type="submit" class="submit-btn">
      <span>Enviar mensaje</span>
      <div class="btn-loader"></div>
    </button>
  </form>
</div>

              
23líneas
730caracteres
HTMLIdioma

Fragmentos de Código Relacionados

Explora packs de plantillas

¿Necesitas bloques más grandes? Descubre landings y colecciones de componentes.

Abrir la biblioteca de plantillas ->