Categoría · Interactivo Nivel de Dificultad · Intermedio Publicado el · 20 de enero de 2025

Componente FAQ Acordeón

Un componente FAQ estilo acordeón clásico con animaciones suaves y secciones expandibles

#faq #accordion #expandable #questions #answers

Diseño Responsivo

Soporte para Modo Oscuro

No

líneas

3

Compatibilidad del Navegador

No

Vista Previa en Vivo

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

600px

Componente FAQ Acordeón

Un componente FAQ estilo acordeón clásico con animaciones suaves, secciones expandibles y una interfaz de usuario intuitiva para mostrar preguntas frecuentes.

Características

  • Animaciones Suaves: Transiciones CSS para abrir/cerrar secciones
  • Expansión Simple/Múltiple: Opción para permitir una o múltiples secciones abiertas
  • Navegación por Teclado: Soporte completo de accesibilidad por teclado
  • Funcionalidad de Búsqueda: Búsqueda integrada para filtrar preguntas
  • Diseño Responsivo: Funciona perfectamente en todos los tamaños de dispositivo
  • Indicadores de Iconos: Señales visuales para estados expandidos/colapsados
  • Estilo Personalizable: Fácil de tematizar y personalizar
  • Auto-desplazamiento: Se desplaza automáticamente a las secciones abiertas

Estructura HTML

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Componente FAQ Acordeón</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="faq-container">
        <!-- Encabezado -->
        <div class="faq-header">
            <h1>Preguntas Frecuentes</h1>
            <p>Encuentra respuestas a preguntas comunes sobre nuestros servicios</p>
            
            <!-- Caja de Búsqueda -->
            <div class="search-container">
                <input type="text" id="faqSearch" placeholder="Buscar preguntas..." class="search-input">
                <svg class="search-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <circle cx="11" cy="11" r="8"></circle>
                    <path d="m21 21-4.35-4.35"></path>
                </svg>
            </div>
            
            <!-- Controles -->
            <div class="faq-controls">
                <button id="expandAll" class="control-btn">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                        <polyline points="6,9 12,15 18,9"></polyline>
                    </svg>
                    Expandir Todo
                </button>
                <button id="collapseAll" class="control-btn">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                        <polyline points="18,15 12,9 6,15"></polyline>
                    </svg>
                    Colapsar Todo
                </button>
            </div>
        </div>

        <!-- FAQ Acordeón -->
        <div class="faq-accordion" id="faqAccordion">
            <div class="faq-item" data-category="general">
                <div class="faq-question" tabindex="0" role="button" aria-expanded="false">
                    <h3>¿Cuál es su política de devoluciones?</h3>
                    <div class="faq-icon">
                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                            <line x1="12" y1="5" x2="12" y2="19"></line>
                            <line x1="5" y1="12" x2="19" y2="12"></line>
                        </svg>
                    </div>
                </div>
                <div class="faq-answer">
                    <div class="faq-content">
                        <p>Ofrecemos una política de devolución de 30 días para todos los artículos no utilizados en su empaque original. Simplemente contacte a nuestro equipo de servicio al cliente para iniciar una devolución, y le proporcionaremos una etiqueta de envío prepagada.</p>
                        <ul>
                            <li>Los artículos deben estar sin usar y en condición original</li>
                            <li>Se requiere el empaque original</li>
                            <li>Reembolso procesado dentro de 5-7 días hábiles</li>
                        </ul>
                    </div>
                </div>
            </div>

            <div class="faq-item" data-category="envio">
                <div class="faq-question" tabindex="0" role="button" aria-expanded="false">
                    <h3>¿Cuánto tiempo toma el envío?</h3>
                    <div class="faq-icon">
                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                            <line x1="12" y1="5" x2="12" y2="19"></line>
                            <line x1="5" y1="12" x2="19" y2="12"></line>
                        </svg>
                    </div>
                </div>
                <div class="faq-answer">
                    <div class="faq-content">
                        <p>Los tiempos de envío varían dependiendo de su ubicación y el método de envío seleccionado:</p>
                        <div class="shipping-table">
                            <div class="shipping-row">
                                <span class="shipping-method">Envío Estándar</span>
                                <span class="shipping-time">5-7 días hábiles</span>
                            </div>
                            <div class="shipping-row">
                                <span class="shipping-method">Envío Express</span>
                                <span class="shipping-time">2-3 días hábiles</span>
                            </div>
                            <div class="shipping-row">
                                <span class="shipping-method">Envío Nocturno</span>
                                <span class="shipping-time">1 día hábil</span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="faq-item" data-category="pago">
                <div class="faq-question" tabindex="0" role="button" aria-expanded="false">
                    <h3>¿Qué métodos de pago aceptan?</h3>
                    <div class="faq-icon">
                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                            <line x1="12" y1="5" x2="12" y2="19"></line>
                            <line x1="5" y1="12" x2="19" y2="12"></line>
                        </svg>
                    </div>
                </div>
                <div class="faq-answer">
                    <div class="faq-content">
                        <p>Aceptamos todos los métodos de pago principales para hacer su experiencia de compra conveniente:</p>
                        <div class="payment-methods">
                            <div class="payment-item">
                                <strong>Tarjetas de Crédito:</strong> Visa, MasterCard, American Express, Discover
                            </div>
                            <div class="payment-item">
                                <strong>Billeteras Digitales:</strong> PayPal, Apple Pay, Google Pay
                            </div>
                            <div class="payment-item">
                                <strong>Transferencia Bancaria:</strong> Transferencias ACH y wire aceptadas
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="faq-item" data-category="cuenta">
                <div class="faq-question" tabindex="0" role="button" aria-expanded="false">
                    <h3>¿Cómo creo una cuenta?</h3>
                    <div class="faq-icon">
                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                            <line x1="12" y1="5" x2="12" y2="19"></line>
                            <line x1="5" y1="12" x2="19" y2="12"></line>
                        </svg>
                    </div>
                </div>
                <div class="faq-answer">
                    <div class="faq-content">
                        <p>¡Crear una cuenta es rápido y fácil! Siga estos simples pasos:</p>
                        <ol>
                            <li>Haga clic en el botón "Registrarse" en la esquina superior derecha</li>
                            <li>Ingrese su dirección de correo electrónico y cree una contraseña segura</li>
                            <li>Verifique su dirección de correo electrónico haciendo clic en el enlace que le enviamos</li>
                            <li>Complete su perfil con su información de envío</li>
                            <li>¡Comience a comprar con su nueva cuenta!</li>
                        </ol>
                        <p><strong>Beneficios de tener una cuenta:</strong></p>
                        <ul>
                            <li>Proceso de pago más rápido</li>
                            <li>Historial de pedidos y seguimiento</li>
                            <li>Descuentos exclusivos para miembros</li>
                            <li>Funcionalidad de lista de deseos</li>
                        </ul>
                    </div>
                </div>
            </div>

            <div class="faq-item" data-category="soporte">
                <div class="faq-question" tabindex="0" role="button" aria-expanded="false">
                    <h3>¿Cómo puedo contactar al soporte al cliente?</h3>
                    <div class="faq-icon">
                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                            <line x1="12" y1="5" x2="12" y2="19"></line>
                            <line x1="5" y1="12" x2="19" y2="12"></line>
                        </svg>
                    </div>
                </div>
                <div class="faq-answer">
                    <div class="faq-content">
                        <p>¡Estamos aquí para ayudar! Puede contactar a nuestro equipo de soporte al cliente a través de múltiples canales:</p>
                        <div class="contact-methods">
                            <div class="contact-item">
                                <div class="contact-icon">📧</div>
                                <div class="contact-info">
                                    <strong>Soporte por Email</strong>
                                    <p>soporte@ejemplo.com</p>
                                    <span>Respuesta dentro de 24 horas</span>
                                </div>
                            </div>
                            <div class="contact-item">
                                <div class="contact-icon">📞</div>
                                <div class="contact-info">
                                    <strong>Soporte Telefónico</strong>
                                    <p>1-800-123-4567</p>
                                    <span>Lun-Vie 9AM-6PM EST</span>
                                </div>
                            </div>
                            <div class="contact-item">
                                <div class="contact-icon">💬</div>
                                <div class="contact-info">
                                    <strong>Chat en Vivo</strong>
                                    <p>Disponible en nuestro sitio web</p>
                                    <span>Lun-Vie 9AM-6PM EST</span>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="faq-item" data-category="general">
                <div class="faq-question" tabindex="0" role="button" aria-expanded="false">
                    <h3>¿Ofrecen envío internacional?</h3>
                    <div class="faq-icon">
                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                            <line x1="12" y1="5" x2="12" y2="19"></line>
                            <line x1="5" y1="12" x2="19" y2="12"></line>
                        </svg>
                    </div>
                </div>
                <div class="faq-answer">
                    <div class="faq-content">
                        <p>¡Sí! Enviamos a más de 50 países en todo el mundo. Las tarifas de envío internacional y los tiempos de entrega varían según el destino.</p>
                        <div class="international-info">
                            <div class="info-section">
                                <h4>Costos de Envío</h4>
                                <p>Calculados al finalizar la compra basados en peso y destino</p>
                            </div>
                            <div class="info-section">
                                <h4>Tiempo de Entrega</h4>
                                <p>7-21 días hábiles dependiendo de la ubicación</p>
                            </div>
                            <div class="info-section">
                                <h4>Aduanas y Aranceles</h4>
                                <p>Responsabilidad del cliente, varía por país</p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- Mensaje Sin Resultados -->
        <div class="no-results" id="noResults" style="display: none;">
            <div class="no-results-icon">
                <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <circle cx="11" cy="11" r="8"></circle>
                    <path d="m21 21-4.35-4.35"></path>
                </svg>
            </div>
            <h3>No se encontraron preguntas</h3>
            <p>Intente ajustar sus términos de búsqueda o navegue todas las preguntas arriba.</p>
        </div>
    </div>

    <script src="script.js"></script>
</body>
</html>

Estilos CSS

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    min-height: 100vh;
    padding: 20px;
}

.faq-container {
    max-width: 800px;
    margin: 0 auto;
    background: white;
    border-radius: 16px;
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
    overflow: hidden;
}

/* Estilos del Encabezado */
.faq-header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 40px 30px;
    text-align: center;
}

.faq-header h1 {
    font-size: 2.5rem;
    font-weight: 700;
    margin-bottom: 8px;
}

.faq-header p {
    font-size: 1.1rem;
    opacity: 0.9;
    margin-bottom: 30px;
}

/* Contenedor de Búsqueda */
.search-container {
    position: relative;
    max-width: 400px;
    margin: 0 auto 20px;
}

.search-input {
    width: 100%;
    padding: 12px 20px 12px 50px;
    border: none;
    border-radius: 25px;
    font-size: 16px;
    background: rgba(255, 255, 255, 0.2);
    color: white;
    backdrop-filter: blur(10px);
    transition: all 0.3s ease;
}

.search-input::placeholder {
    color: rgba(255, 255, 255, 0.7);
}

.search-input:focus {
    outline: none;
    background: rgba(255, 255, 255, 0.3);
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.2);
}

.search-icon {
    position: absolute;
    left: 18px;
    top: 50%;
    transform: translateY(-50%);
    color: rgba(255, 255, 255, 0.7);
}

/* Controles */
.faq-controls {
    display: flex;
    gap: 12px;
    justify-content: center;
    flex-wrap: wrap;
}

.control-btn {
    background: rgba(255, 255, 255, 0.2);
    border: none;
    color: white;
    padding: 8px 16px;
    border-radius: 20px;
    font-size: 14px;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 6px;
    transition: all 0.3s ease;
    backdrop-filter: blur(10px);
}

.control-btn:hover {
    background: rgba(255, 255, 255, 0.3);
    transform: translateY(-1px);
}

/* Estilos del Acordeón */
.faq-accordion {
    padding: 0;
}

.faq-item {
    border-bottom: 1px solid #e2e8f0;
    transition: all 0.3s ease;
}

.faq-item:last-child {
    border-bottom: none;
}

.faq-item.active {
    background: #f8fafc;
}

.faq-question {
    padding: 24px 30px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    transition: all 0.3s ease;
    outline: none;
}

.faq-question:hover {
    background: #f8fafc;
}

.faq-question:focus {
    background: #f1f5f9;
    box-shadow: inset 0 0 0 2px #667eea;
}

.faq-question h3 {
    font-size: 1.1rem;
    font-weight: 600;
    color: #1e293b;
    margin: 0;
    flex: 1;
    text-align: left;
}

.faq-icon {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: #667eea;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s ease;
    flex-shrink: 0;
    margin-left: 16px;
}

.faq-icon svg {
    color: white;
    transition: transform 0.3s ease;
}

.faq-item.active .faq-icon {
    background: #4f46e5;
    transform: rotate(45deg);
}

.faq-answer {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
}

.faq-item.active .faq-answer {
    max-height: 1000px;
}

.faq-content {
    padding: 0 30px 24px;
    color: #64748b;
    line-height: 1.6;
}

.faq-content p {
    margin-bottom: 16px;
}

.faq-content ul,
.faq-content ol {
    margin: 16px 0;
    padding-left: 20px;
}

.faq-content li {
    margin-bottom: 8px;
}

/* Tabla de Envío */
.shipping-table {
    background: #f8fafc;
    border-radius: 8px;
    padding: 16px;
    margin: 16px 0;
}

.shipping-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 0;
    border-bottom: 1px solid #e2e8f0;
}

.shipping-row:last-child {
    border-bottom: none;
}

.shipping-method {
    font-weight: 500;
    color: #374151;
}

.shipping-time {
    color: #667eea;
    font-weight: 500;
}

/* Métodos de Pago */
.payment-methods {
    margin: 16px 0;
}

.payment-item {
    background: #f8fafc;
    padding: 12px 16px;
    border-radius: 8px;
    margin-bottom: 8px;
    border-left: 4px solid #667eea;
}

.payment-item strong {
    color: #374151;
    display: block;
    margin-bottom: 4px;
}

/* Métodos de Contacto */
.contact-methods {
    margin: 20px 0;
}

.contact-item {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    padding: 16px;
    background: #f8fafc;
    border-radius: 12px;
    margin-bottom: 12px;
}

.contact-icon {
    font-size: 24px;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: white;
    border-radius: 50%;
    flex-shrink: 0;
}

.contact-info strong {
    color: #374151;
    display: block;
    margin-bottom: 4px;
}

.contact-info p {
    margin: 0;
    color: #667eea;
    font-weight: 500;
}

.contact-info span {
    font-size: 14px;
    color: #64748b;
}

/* Información Internacional */
.international-info {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 16px;
    margin: 20px 0;
}

.info-section {
    background: #f8fafc;
    padding: 16px;
    border-radius: 8px;
    border-left: 4px solid #667eea;
}

.info-section h4 {
    color: #374151;
    margin-bottom: 8px;
    font-size: 16px;
}

.info-section p {
    margin: 0;
    font-size: 14px;
}

/* Sin Resultados */
.no-results {
    text-align: center;
    padding: 60px 30px;
    color: #64748b;
}

.no-results-icon {
    margin-bottom: 20px;
    opacity: 0.5;
}

.no-results h3 {
    font-size: 1.5rem;
    margin-bottom: 8px;
    color: #374151;
}

.no-results p {
    font-size: 1rem;
}

/* Animaciones */
@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.faq-item {
    animation: fadeIn 0.3s ease;
}

/* Diseño Responsivo */
@media (max-width: 768px) {
    body {
        padding: 10px;
    }
    
    .faq-container {
        border-radius: 12px;
    }
    
    .faq-header {
        padding: 30px 20px;
    }
    
    .faq-header h1 {
        font-size: 2rem;
    }
    
    .faq-question {
        padding: 20px;
    }
    
    .faq-content {
        padding: 0 20px 20px;
    }
    
    .faq-question h3 {
        font-size: 1rem;
    }
    
    .contact-item {
        flex-direction: column;
        text-align: center;
    }
    
    .international-info {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 480px) {
    .faq-header h1 {
        font-size: 1.75rem;
    }
    
    .faq-controls {
        flex-direction: column;
        align-items: center;
    }
    
    .control-btn {
        width: 100%;
        max-width: 200px;
        justify-content: center;
    }
}

Funcionalidad JavaScript

class AccordionFAQ {
    constructor() {
        this.accordion = document.getElementById('faqAccordion');
        this.searchInput = document.getElementById('faqSearch');
        this.expandAllBtn = document.getElementById('expandAll');
        this.collapseAllBtn = document.getElementById('collapseAll');
        this.noResults = document.getElementById('noResults');
        this.faqItems = Array.from(document.querySelectorAll('.faq-item'));
        
        this.init();
    }
    
    init() {
        this.bindEvents();
        this.setupKeyboardNavigation();
    }
    
    bindEvents() {
        // Eventos de clic del acordeón
        this.accordion.addEventListener('click', (e) => {
            const question = e.target.closest('.faq-question');
            if (question) {
                this.toggleItem(question.parentElement);
            }
        });
        
        // Funcionalidad de búsqueda
        this.searchInput.addEventListener('input', (e) => {
            this.filterQuestions(e.target.value);
        });
        
        // Botones de control
        this.expandAllBtn.addEventListener('click', () => this.expandAll());
        this.collapseAllBtn.addEventListener('click', () => this.collapseAll());
    }
    
    setupKeyboardNavigation() {
        this.accordion.addEventListener('keydown', (e) => {
            const question = e.target.closest('.faq-question');
            if (!question) return;
            
            switch (e.key) {
                case 'Enter':
                case ' ':
                    e.preventDefault();
                    this.toggleItem(question.parentElement);
                    break;
                case 'ArrowDown':
                    e.preventDefault();
                    this.focusNext(question);
                    break;
                case 'ArrowUp':
                    e.preventDefault();
                    this.focusPrevious(question);
                    break;
                case 'Home':
                    e.preventDefault();
                    this.focusFirst();
                    break;
                case 'End':
                    e.preventDefault();
                    this.focusLast();
                    break;
            }
        });
    }
    
    toggleItem(item) {
        const isActive = item.classList.contains('active');
        const question = item.querySelector('.faq-question');
        
        if (isActive) {
            this.closeItem(item);
        } else {
            this.openItem(item);
        }
        
        // Actualizar atributos ARIA
        question.setAttribute('aria-expanded', !isActive);
        
        // Auto-desplazamiento al elemento abierto
        if (!isActive) {
            setTimeout(() => {
                item.scrollIntoView({
                    behavior: 'smooth',
                    block: 'nearest'
                });
            }, 150);
        }
    }
    
    openItem(item) {
        item.classList.add('active');
        const answer = item.querySelector('.faq-answer');
        const content = item.querySelector('.faq-content');
        
        // Establecer max-height a la altura del contenido para animación suave
        answer.style.maxHeight = content.scrollHeight + 'px';
        
        // Agregar clase de animación
        item.style.animation = 'fadeIn 0.3s ease';
    }
    
    closeItem(item) {
        item.classList.remove('active');
        const answer = item.querySelector('.faq-answer');
        answer.style.maxHeight = '0';
    }
    
    expandAll() {
        const visibleItems = this.faqItems.filter(item => 
            item.style.display !== 'none'
        );
        
        visibleItems.forEach((item, index) => {
            setTimeout(() => {
                if (!item.classList.contains('active')) {
                    this.openItem(item);
                    const question = item.querySelector('.faq-question');
                    question.setAttribute('aria-expanded', 'true');
                }
            }, index * 100);
        });
    }
    
    collapseAll() {
        const activeItems = this.faqItems.filter(item => 
            item.classList.contains('active')
        );
        
        activeItems.forEach((item, index) => {
            setTimeout(() => {
                this.closeItem(item);
                const question = item.querySelector('.faq-question');
                question.setAttribute('aria-expanded', 'false');
            }, index * 50);
        });
    }
    
    filterQuestions(searchTerm) {
        const term = searchTerm.toLowerCase().trim();
        let visibleCount = 0;
        
        this.faqItems.forEach(item => {
            const question = item.querySelector('h3').textContent.toLowerCase();
            const answer = item.querySelector('.faq-content').textContent.toLowerCase();
            
            const matches = question.includes(term) || answer.includes(term);
            
            if (matches || term === '') {
                item.style.display = 'block';
                visibleCount++;
                
                // Resaltar términos de búsqueda
                if (term !== '') {
                    this.highlightSearchTerm(item, term);
                } else {
                    this.removeHighlights(item);
                }
            } else {
                item.style.display = 'none';
                // Cerrar elementos ocultos
                if (item.classList.contains('active')) {
                    this.closeItem(item);
                }
            }
        });
        
        // Mostrar/ocultar mensaje sin resultados
        if (visibleCount === 0 && term !== '') {
            this.noResults.style.display = 'block';
            this.accordion.style.display = 'none';
        } else {
            this.noResults.style.display = 'none';
            this.accordion.style.display = 'block';
        }
    }
    
    highlightSearchTerm(item, term) {
        const question = item.querySelector('h3');
        const content = item.querySelector('.faq-content');
        
        // Resaltado simple (en una aplicación real, use una solución más robusta)
        const highlightText = (element) => {
            const text = element.textContent;
            const regex = new RegExp(`(${term})`, 'gi');
            const highlightedText = text.replace(regex, '<mark>$1</mark>');
            
            if (text !== highlightedText) {
                element.innerHTML = highlightedText;
            }
        };
        
        highlightText(question);
    }
    
    removeHighlights(item) {
        const marks = item.querySelectorAll('mark');
        marks.forEach(mark => {
            mark.outerHTML = mark.textContent;
        });
    }
    
    focusNext(currentQuestion) {
        const questions = Array.from(document.querySelectorAll('.faq-question'));
        const visibleQuestions = questions.filter(q => 
            q.closest('.faq-item').style.display !== 'none'
        );
        const currentIndex = visibleQuestions.indexOf(currentQuestion);
        const nextIndex = (currentIndex + 1) % visibleQuestions.length;
        visibleQuestions[nextIndex].focus();
    }
    
    focusPrevious(currentQuestion) {
        const questions = Array.from(document.querySelectorAll('.faq-question'));
        const visibleQuestions = questions.filter(q => 
            q.closest('.faq-item').style.display !== 'none'
        );
        const currentIndex = visibleQuestions.indexOf(currentQuestion);
        const prevIndex = currentIndex === 0 ? visibleQuestions.length - 1 : currentIndex - 1;
        visibleQuestions[prevIndex].focus();
    }
    
    focusFirst() {
        const firstVisible = document.querySelector('.faq-item:not([style*="display: none"]) .faq-question');
        if (firstVisible) firstVisible.focus();
    }
    
    focusLast() {
        const questions = Array.from(document.querySelectorAll('.faq-question'));
        const visibleQuestions = questions.filter(q => 
            q.closest('.faq-item').style.display !== 'none'
        );
        const lastVisible = visibleQuestions[visibleQuestions.length - 1];
        if (lastVisible) lastVisible.focus();
    }
}

// Inicializar el acordeón cuando el DOM esté cargado
document.addEventListener('DOMContentLoaded', () => {
    new AccordionFAQ();
    
    // Agregar desplazamiento suave para mejor UX
    document.documentElement.style.scrollBehavior = 'smooth';
    
    // Agregar animación de carga
    const faqItems = document.querySelectorAll('.faq-item');
    faqItems.forEach((item, index) => {
        item.style.opacity = '0';
        item.style.transform = 'translateY(20px)';
        
        setTimeout(() => {
            item.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
            item.style.opacity = '1';
            item.style.transform = 'translateY(0)';
        }, index * 100);
    });
});

Ejemplos de Uso

Implementación Básica

// Inicializar el acordeón
const faq = new AccordionFAQ();

// Abrir programáticamente un elemento específico
faq.openItem(document.querySelector('.faq-item[data-category="general"]'));

// Buscar programáticamente
faq.filterQuestions('envío');

Configuración Personalizada

// Opciones de configuración extendidas
const faq = new AccordionFAQ({
    allowMultiple: true,        // Permitir múltiples elementos abiertos
    autoClose: false,          // No cerrar automáticamente otros elementos
    animationDuration: 300,    // Duración de animación en ms
    scrollOffset: 100,         // Desplazamiento al abrir elementos
    highlightSearch: true      // Resaltar términos de búsqueda
});

Integración con Analytics

// Rastrear interacciones FAQ
class AnalyticsAccordionFAQ extends AccordionFAQ {
    toggleItem(item) {
        super.toggleItem(item);
        
        const question = item.querySelector('h3').textContent;
        const isOpening = item.classList.contains('active');
        
        // Enviar evento de analytics
        gtag('event', 'faq_interaction', {
            'question': question,
            'action': isOpening ? 'open' : 'close'
        });
    }
}

Compatibilidad del Navegador

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

Características Incluidas

✅ Animaciones suaves de acordeón
✅ Funcionalidad de búsqueda con resaltado
✅ Soporte de navegación por teclado
✅ Controles expandir/colapsar todo
✅ Auto-desplazamiento a secciones abiertas
✅ Diseño responsivo
✅ Atributos de accesibilidad ARIA
✅ Opciones de estilo personalizado
✅ Sin dependencias externas
✅ Estructura amigable para SEO
✅ Interfaz táctil amigable
✅ Animaciones de carga

Opciones de Personalización

  • Colores: Modificar propiedades personalizadas CSS para tematización
  • Velocidad de Animación: Ajustar duraciones de transición
  • Diseño: Personalizar espaciado y tipografía
  • Iconos: Reemplazar iconos SVG con personalizados
  • Búsqueda: Configurar comportamiento de búsqueda y resaltado
  • Teclado: Personalizar atajos de teclado

Este componente FAQ acordeón proporciona una forma profesional y amigable para el usuario de mostrar preguntas frecuentes con animaciones suaves y soporte completo de accesibilidad.

HTML

1

líneas

CSS

1

líneas

JavaScript

1

líneas


                <!-- HTML content will be added here -->

              
1líneas
41caracteres
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 →