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
Diseño Responsivo
Sí
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.
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 requeridosobtenerDatosFormulario(formulario)- Obtiene todos los datos del formularioestablecerDatosFormulario(idFormulario, datos)- Establece valores en el formularioresetearFormulario(formulario)- Resetea el formulario a su estado inicial
Eventos
input- Validación en tiempo real mientras se escribeblur- Validación cuando el campo pierde el focosubmit- Manejo del envío del formulariochange- 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>