Introducción
Este tutorial explica paso a paso cómo diseñar un layout de preguntas frecuentes (FAQ) tipo acordeón usando las etiquetas semánticas ltdetailsgt y ltsummarygt, y cómo integrarlo correctamente en un sitio WordPress. Cubriremos la estructura HTML, el CSS para un aspecto moderno, mejoras con JavaScript (animación, comportamiento de abrir/cerrar, accesibilidad y navegación por teclado) y la forma recomendada de insertar el bloque en WordPress mediante un shortcode y el correcto registro/encolado de estilos y scripts. Todos los ejemplos de código se incluyen listos para copiar y pegar.
Por qué usar details/summary
- Semántica y accesibilidad: Son elementos nativos del navegador pensados para mostrar/ocultar contenido, reconocidos por lectores de pantalla y con comportamiento por defecto en teclado.
- Progresive enhancement: Funcionan sin JavaScript. Con JS podemos mejorar la experiencia (animaciones, control de multi/único acordeón) sin romper la funcionalidad base.
- Menor complejidad: Evitas reinventar pestañas y roles ARIA manuales.
Estructura HTML básica
Ejemplo mínimo de un elemento FAQ con details/summary:
ltdetailsgt ltsummarygt¿Cómo puedo crear una cuenta?lt/summarygt ltpgtPara crear una cuenta, haz clic en lta href=#gtRegistrolt/agt y completa el formulario.lt/pgt lt/detailsgt
Para varias preguntas, se repite el bloque ltdetailsgt:
ltsection class=faqgt
ltdetailsgt
ltsummarygt¿Cómo puedo crear una cuenta?lt/summarygt
ltpgtPara crear una cuenta, haz clic en lta href=#gtRegistrolt/agt y completa el formulario.lt/pgt
lt/detailsgt
ltdetailsgt
ltsummarygt¿Cómo restablezco mi contraseña?lt/summarygt
ltpgtUsa la opción quot¿Olvidaste tu contraseña?quot en la pantalla de acceso.lt/pgt
lt/detailsgt
lt/sectiongt
Estilos CSS recomendados
Objetivos del CSS: apariencia de acordeón, iconos, transiciones suaves, respetar prefers-reduced-motion y estilos de foco accesibles.
/ Estilos base para la FAQ /
.faq {
max-width: 800px
margin: 0 auto
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial
}
/ Resetea el estilo por defecto del summary /
.faq summary {
list-style: none
cursor: pointer
display: flex
align-items: center
justify-content: space-between
padding: 1rem
background: #f7f7f8
border: 1px solid #e3e3e6
border-radius: 6px
margin-top: 0.8rem
font-weight: 600
transition: background .15s ease
outline: none
}
/ Indicador (flecha) con pseudo-elemento /
.faq summary::marker { display: none } / evita marcador por defecto /
.faq summary::after {
content:
width: 1rem
height: 1rem
transform: rotate(0deg)
transition: transform .18s ease
mask: url(data:image/svg xmlutf8,ltsvg xmlns=http://www.w3.org/2000/svg viewBox=0 0 24 24gtltpath fill=black d=M7 10l5 5 5-5z/gtlt/svggt) center / contain no-repeat
background-color: #333
margin-left: 1rem
}
/ Estado abierto: gira la flecha /
.faq details[open] summary::after {
transform: rotate(180deg)
}
/ Contenedor del contenido oculto /
.faq details > p {
margin: 0
padding: 1rem
border: 1px solid #e3e3e6
border-top: none
background: #fff
line-height: 1.6
}
/ Foco accesible /
.faq summary:focus {
box-shadow: 0 0 0 3px rgba(50,115,220,0.15)
}
/ Reducir animaciones si el usuario lo solicita /
@media (prefers-reduced-motion: reduce) {
.faq summary,
.faq summary::after {
transition: none
}
}
Animación suave de apertura (JS)
Por defecto el elemento ltdetailsgt abre/cierra sin animación transversal. Para animarlo con altura dinámica se puede usar JavaScript que calcule la altura del contenido. El siguiente script añade animación y opcionalmente fuerza que solo uno esté abierto a la vez (comportamiento accordion).
/ Animación para details: slide-down/slide-up y control de un solo abierto /
document.addEventListener(DOMContentLoaded, function () {
const detailsList = document.querySelectorAll(.faq details)
detailsList.forEach(details =gt {
const summary = details.querySelector(summary)
const content = Array.from(details.childNodes).filter(n =gt n.nodeType === 1 n.tagName.toLowerCase() !== summary)[0]
// Para animar, guardamos la altura cuando está abierto
details.addEventListener(toggle, (e) =gt {
if (details.open) {
// Si queremos un solo abierto a la vez:
detailsList.forEach(other =gt {
if (other !== details) other.open = false
})
content.style.display = block
const h = content.getBoundingClientRect().height
content.style.height = 0px
// Forzar repaint
content.offsetHeight
content.style.transition = height 220ms ease
content.style.height = h px
content.addEventListener(transitionend, function te() {
content.style.height =
content.style.transition =
content.removeEventListener(transitionend, te)
})
} else {
const h = content.getBoundingClientRect().height
content.style.height = h px
// Forzar repaint
content.offsetHeight
content.style.transition = height 180ms ease
content.style.height = 0px
content.addEventListener(transitionend, function tc() {
content.style.display =
content.style.height =
content.style.transition =
content.removeEventListener(transitionend, tc)
})
}
})
})
})
Notas importantes sobre el script
- No rompas la funcionalidad básica: si JS no se carga, details/summary seguirán funcionando sin animación.
- Usamos toggle para detectar apertura/cierre es compatible con navegadores modernos. Para compatibilidad con navegadores antiguos, añade comprobaciones o polyfills.
- Si quieres permitir varios abiertos simultáneamente, elimina el bloque que cierra otros detalles.
Accesibilidad y navegación por teclado
Detalles clave para que una FAQ sea accesible:
- El elemento ltsummarygt ya es focusable y responde a Enter/Space. Mantén estilos de foco claros.
- Evita usar roles ARIA adicionales por defecto details/summary ya proveen semántica. Si añades roles, asegúrate de no introducir contradicciones.
- Si aplicas animaciones, respetar prefers-reduced-motion para usuarios que soliciten menos movimiento.
- Si creas un componente complejo que oculta/gestiona estados manualmente (sin details), implementa roles ARIA apropiados y manejo de teclado (Up/Down para navegar entre sumarios, Home/End para ir a inicio/fin, etc.).
Integración en WordPress: Shortcode Enqueue
A continuación un ejemplo de cómo registrar un shortcode que renderice una lista de FAQs, y cómo encolar los estilos y scripts desde functions.php.
/ functions.php (o un plugin propio) /
/ Encolado de estilos y scripts /
function mi_faq_enqueue_assets() {
wp_enqueue_style( mi-faq-style, get_stylesheet_directory_uri() . /assets/mi-faq.css, array(), 1.0.0 )
wp_enqueue_script( mi-faq-script, get_stylesheet_directory_uri() . /assets/mi-faq.js, array(), 1.0.0, true )
}
add_action( wp_enqueue_scripts, mi_faq_enqueue_assets )
/ Shortcode [mi_faq] que acepta contenido en formato simple (puedes adaptar a ACF o bloques) /
function mi_faq_shortcode( atts = array(), content = null ) {
// Ejemplo simple: parseamos un contenido con separador
// Uso: [mi_faq]Pregunta 1::Respuesta 1Pregunta 2::Respuesta 2[/mi_faq]
raw = do_shortcode( content )
items = array_filter( array_map( trim, explode(, raw) ) )
output =
foreach ( items as item ) {
list( q, a ) = array_map( trim, explode(::, item . ::) )
output .=
output .= . wp_kses_post( q ) .
output .= . wp_kses_post( wpautop( a ) ) .
output .=
}
output .=
return output
}
add_shortcode( mi_faq, mi_faq_shortcode )
Explicación breve:
- Encolamos un CSS y un JS desde la carpeta assets del tema. En un plugin cambia get_stylesheet_directory_uri() por plugin_dir_url(__FILE__).
- El shortcode interpreta un formato sencillo: Pregunta::Respuesta separado por . Esto es solo un ejemplo en producción usa ACF, bloques o metaboxes para contenido estructurado.
- Usamos wp_kses_post y wpautop para permitir HTML seguro y automatic paragraphs.
Ejemplo completo de CSS y JS listo para WordPress
CSS (guardar en assets/mi-faq.css):
/ Contenido similar al ejemplo anterior, adaptado al selector .faq /
.faq { max-width: 900px margin: 1.5rem auto font-family: sans-serif }
.faq summary { ... } / Usa el CSS del bloque anterior tal cual /
JS (guardar en assets/mi-faq.js):
/ Copia el script de animación mostrado anteriormente /
document.addEventListener(DOMContentLoaded, function () {
// código de animación y control de un solo abierto
})
Uso práctico en una entrada o página
Ejemplo de uso del shortcode dentro del editor de WordPress (editor clásico o bloque shortcode):
[mi_faq]¿Cómo creo una cuenta?::Ve a Registro y completa el formulario.¿Cómo contacto soporte?::Usa el formulario en la página de contacto.[/mi_faq]
Variantes y mejoras avanzadas
- Iconos SVG inline: para mayor control visual, incluye SVG en el summary (ojo con la accesibilidad, añade aria-hidden=true en decorativos).
- Contenido dinámico: carga FAQs desde una taxonomía o post type y genera el shortcode dinámicamente para gestión desde el panel.
- Gutenberg block: si prefieres un bloque, crea un bloque dinámico que renderice lo mismo y permita edición visual.
- Control de estado con URL hash: puedes leer el hash de la URL para abrir una pregunta concreta al cargar la página.
Pruebas y depuración
- Prueba sin JavaScript para asegurarte de que details/summary permiten acceder a todo el contenido.
- Comprueba comportamiento en móviles y teclados: Tab para foco, Enter/Space para abrir, y lectores de pantalla.
- Verifica que los assets estén correctamente encolados y que no haya conflicto con otros scripts del tema o plugins.
- Revisa la política de caché/optimización (minificación, concatenación) de tu sitio para evitar que el script no cargue o se ejecute fuera de orden.
Conclusión
Usar ltdetailsgt y ltsummarygt te da una base semántica y accesible para construir FAQs tipo acordeón. Añadiendo CSS y una capa leve de JavaScript obtendrás una experiencia moderna con animaciones y control de estado. En WordPress lo más sencillo es exponer el conjunto mediante un shortcode o un bloque, y encolar correctamente los assets. Siguiendo las prácticas de accesibilidad (foco visible, respects prefers-reduced-motion, no eliminar funcionalidad sin JS) tendrás un componente robusto y usable para tu sitio.
Leave a Reply