Introducción
Este tutorial explica paso a paso cómo crear una sección de FAQ (preguntas frecuentes) en WordPress que muestre dos columnas en pantallas de escritorio y una columna en dispositivos móviles. Incluye el marcado HTML recomendado, estilos CSS para la disposición y la interacción (acordeón accesible) y el código PHP para integrarlo como un shortcode o parte de tu tema. Todos los ejemplos de código están preparados para copiar y pegar.
Resumen de la solución
- Estructura HTML semántica para cada pregunta y respuesta.
- CSS con grid para disponer 2 columnas en desktop y 1 columna en móvil.
- JavaScript ligero para comportamiento de acordeón accesible (teclado y aria).
- Integración en WordPress mediante un shortcode y encolado adecuado de estilos y scripts.
Requisitos previos
- Tener acceso al tema activo (mejor en un tema hijo) o crear un plugin sencillo para añadir el shortcode.
- Conocimientos básicos de PHP para editar functions.php o crear un archivo de plugin.
- Opcional: tener las preguntas almacenadas como entradas de un CPT faq o ACF para gestión desde el admin.
1. Marcado HTML recomendado
Usamos una estructura simple con una lista de elementos .faq-item. La pregunta será un elemento interactivo (button) para accesibilidad y la respuesta será un contenedor que puede abrirse/cerrarse. Ejemplo de HTML:
ltsection class=faq-section aria-label=Preguntas frecuentesgt
ltul class=faq-gridgt
ltli class=faq-itemgt
ltbutton class=faq-question aria-expanded=false aria-controls=faq1gt¿Cómo puedo cancelar mi suscripción?lt/buttongt
ltdiv id=faq1 class=faq-answer hiddengt
ltpgtPara cancelar, accede a tu cuenta y selecciona ltstronggtCancelar suscripciónlt/stronggt en el panel de facturación.lt/pgt
lt/divgt
lt/ligt
ltli class=faq-itemgt
ltbutton class=faq-question aria-expanded=false aria-controls=faq2gt¿Ofrecen soporte 24/7?lt/buttongt
ltdiv id=faq2 class=faq-answer hiddengt
ltpgtNuestro soporte está disponible de lunes a viernes. Para urgencias, usa el formulario de contacto.lt/pgt
lt/divgt
lt/ligt
lt!-- Más items... --gt
lt/ulgt
lt/sectiongt
Notas sobre el marcado
- Usamos ltbuttongt para las preguntas para asegurar que sean accesibles por teclado y que funcionen con lector de pantalla.
- El atributo aria-controls enlaza la pregunta con su respuesta y aria-expanded indica el estado.
- Utilizamos el atributo hidden en la respuesta por defecto el script lo gestionará para mostrar/ocultar.
2. CSS: dos columnas en desktop y una en móvil
Este CSS crea una cuadrícula responsive y estilos básicos para el acordeón. Ajusta colores, tipografías y valores a tu diseño.
/ Contenedor grid: 2 columnas en escritorio, 1 en móvil /
.faq-grid {
display: grid
grid-template-columns: repeat(2, 1fr)
gap: 1.25rem / espacio entre items /
list-style: none
margin: 0
padding: 0
}
/ Item /
.faq-item {
border: 1px solid #e0e0e0
border-radius: 6px
overflow: hidden
background: #fff
}
/ Pregunta (botón) /
.faq-question {
width: 100%
text-align: left
padding: 1rem
background: transparent
border: none
cursor: pointer
font-weight: 600
font-size: 1rem
display: flex
justify-content: space-between
align-items: center
}
/ Indicador visual (puedes usar ::after para un icono) /
.faq-question::after {
content:
margin-left: 1rem
transition: transform .25s ease
}
/ Cuando está expandido se rota el indicador /
.faq-question[aria-expanded=true]::after {
content: -
transform: rotate(0deg)
}
/ Respuesta (por defecto oculta) /
.faq-answer {
padding: 0 1rem 1rem 1rem
line-height: 1.5
}
/ Si está hidden, manténlo oculto mediante display o max-height (mejor para transiciones) /
.faq-answer[hidden] {
display: none
}
/ <= 767px: una columna /
@media (max-width: 767px) {
.faq-grid {
grid-template-columns: 1fr
}
}
Transiciones y animación (opcional)
Si deseas animar la apertura/cierre, cambia hide/show con max-height y overflow ten en cuenta que requiere medir la altura o usar transitions con max-height suficientemente grande.
/ Alternativa con animación (requiere JS que gestione la altura) /
.faq-answer {
max-height: 0
overflow: hidden
transition: max-height .35s ease
padding: 0 1rem
}
.faq-item.open .faq-answer {
max-height: 400px / suficiente para el contenido mejor calcular en JS /
padding: 1rem
}
3. JavaScript: comportamiento del acordeón y accesibilidad
Este script añade la lógica para abrir/cerrar items, actualizar aria-expanded y permitir navegación con teclado. Lo ideal es encolarlo con wp_enqueue_script (ver sección PHP).
document.addEventListener(DOMContentLoaded, function() {
var faqButtons = document.querySelectorAll(.faq-question)
faqButtons.forEach(function(btn) {
btn.addEventListener(click, function() {
var expanded = btn.getAttribute(aria-expanded) === true
var controlledId = btn.getAttribute(aria-controls)
var controlledEl = document.getElementById(controlledId)
// Toggle estado
if (expanded) {
btn.setAttribute(aria-expanded, false)
if (controlledEl) {
controlledEl.setAttribute(hidden, )
}
btn.closest(.faq-item)?.classList.remove(open)
} else {
btn.setAttribute(aria-expanded, true)
if (controlledEl) {
controlledEl.removeAttribute(hidden)
}
btn.closest(.faq-item)?.classList.add(open)
}
})
// Soporte teclado: abrir con Enter o Space (button ya lo maneja por defecto)
// Si deseas añadir navegación entre preguntas, implementa manejadores de flechas.
})
// Opcional: cerrar otros al abrir uno (acordeón exclusivo)
// Si quieres solo uno abierto a la vez:
function enableExclusiveOpen() {
faqButtons.forEach(function(btn) {
btn.addEventListener(click, function() {
if (btn.getAttribute(aria-expanded) === true) {
faqButtons.forEach(function(other) {
if (other !== btn) {
other.setAttribute(aria-expanded, false)
var id = other.getAttribute(aria-controls)
var el = document.getElementById(id)
if (el) el.setAttribute(hidden, )
other.closest(.faq-item)?.classList.remove(open)
}
})
}
})
})
}
// Llamar a enableExclusiveOpen() si se desea ese comportamiento
})
4. Integración en WordPress: shortcode y encolado de assets
Ejemplo de cómo registrar un shortcode [faq_section] que imprime el marcado y encola CSS/JS desde functions.php o un plugin.
/ En functions.php o en un plugin /
function mytheme_enqueue_faq_assets() {
// Estilos
wp_enqueue_style(my-faq-style, get_stylesheet_directory_uri() . /assets/css/faq.css, array(), 1.0)
// Script (coloca en assets/js/faq.js)
wp_enqueue_script(my-faq-script, get_stylesheet_directory_uri() . /assets/js/faq.js, array(), 1.0, true)
}
add_action(wp_enqueue_scripts, mytheme_enqueue_faq_assets)
function mytheme_faq_shortcode(atts) {
// Puedes obtener preguntas desde ACF o un CPT. Aquí un ejemplo estático con 2 items.
ob_start()
?>
ltsection class=faq-section aria-label=Preguntas frecuentesgt
ltul class=faq-gridgt
ltli class=faq-itemgt
ltbutton class=faq-question aria-expanded=false aria-controls=faq1gt¿Cómo puedo cancelar mi suscripción?lt/buttongt
ltdiv id=faq1 class=faq-answer hiddengt
ltpgtPara cancelar, accede a tu cuenta y selecciona ltstronggtCancelar suscripciónlt/stronggt en el panel de facturación.lt/pgt
lt/divgt
lt/ligt
ltli class=faq-itemgt
ltbutton class=faq-question aria-expanded=false aria-controls=faq2gt¿Ofrecen soporte 24/7?lt/buttongt
ltdiv id=faq2 class=faq-answer hiddengt
ltpgtNuestro soporte está disponible de lunes a viernes. Para urgencias, usa el formulario de contacto.lt/pgt
lt/divgt
lt/ligt
lt/ulgt
lt/sectiongt
lt?php
return ob_get_clean()
}
add_shortcode(faq_section, mytheme_faq_shortcode)
Ejemplo: obtener FAQs desde un Custom Post Type faq
Si tienes un CPT llamado faq, reemplaza el contenido estático por una consulta WP_Query. Ejemplo de loop:
query = new WP_Query(array(
post_type => faq,
posts_per_page => -1,
orderby => menu_order,
order => ASC,
))
if (query->have_posts()) {
echo ltul class=faq-gridgt
i = 1
while (query->have_posts()) {
query->the_post()
id = faq . i
echo ltli class=faq-itemgt
echo ltbutton class=faq-question aria-expanded=false aria-controls= . esc_attr(id) . gt . get_the_title() . lt/buttongt
echo ltdiv id= . esc_attr(id) . class=faq-answer hiddengt . apply_filters(the_content, get_the_content()) . lt/divgt
echo lt/ligt
i
}
echo lt/ulgt
wp_reset_postdata()
}
5. Buenas prácticas y accesibilidad
- Usar elementos interactivos nativos: button para preguntas (mejor que div con role).
- Aria: mantener aria-expanded actualizado y aria-controls apuntando al id del panel.
- Teclado: asegúrate que el foco sea visible y que Enter/Space activen el botón (esto lo hace el button por defecto).
- Lectores de pantalla: evita ocultar información importante con solo display:none sin aria apropiada hidden es aceptable si lo controlas.
- Responsividad: prueba en varios anchos y con fuentes grandes para asegurar que no se rompa la disposición de columnas.
6. Variantes y mejoras
- Hacer que al abrir una pregunta se cierre la anterior para simular un acordeón exclusivo (ver la función enableExclusiveOpen() en el JS).
- Animaciones más suaves con height calculado dinámicamente en JS para evitar fijar max-height grandes.
- Mostrar un icono SVG en lugar del /-, y rotarlo con CSS.
- Integración con Gutenberg: crear un bloque que gestione la lista de FAQs desde el editor.
- Lazy-load de respuestas largas o cargar contenido dinámicamente si hay muchas FAQs.
7. Problemas comunes y soluciones
- El CSS no se aplica: comprueba que la hoja esté encolada y que no haya reglas más específicas del tema que la estén sobrescribiendo. Usa selectores más específicos o !important con moderación.
- JS no funciona: verifica que el script se encole en el footer (último parámetro true) y que no haya errores JS en la consola que detengan la ejecución.
- IDs duplicados: si generas IDs dinámicamente desde un loop asegúrate de que cada faq tenga un id único (ej. faq1, faq2...).
- Problemas de accesibilidad: prueba con lectores de pantalla y navegación por teclado, asegurando que aria-expanded y hidden estén sincronizados.
Conclusión
Con una estructura semántica simple, CSS Grid para la disposición responsive y un JavaScript ligero para la interacción accesible, puedes implementar una sección de FAQ que muestre dos columnas en escritorio y una en móvil. Integra el código en WordPress mediante un shortcode o plantilla, y adapta los estilos al diseño de tu sitio. Los fragmentos de ejemplo incluidos cubren el marcado, los estilos, el script y la integración PHP listos para usar.
Leave a Reply