Introducción
Un índice de contenidos flotante (tabla de contenidos, TOC) mejora la navegación en artículos largos y favorece la experiencia del lector. En WordPress, se puede lograr un índice que se mantenga visible usando CSS position: sticky, con ventajas frente a soluciones basadas únicamente en JavaScript: es más sencillo, mejor rendimiento y compatibilidad con navegadores modernos. Este artículo explica con todo lujo de detalles cómo implementarlo, problemas habituales (barra de administración, contenedores con overflow), accesibilidad y ejemplos prácticos para integrarlo en temas o plugins de WordPress.
Conceptos clave
- position: sticky: combina comportamiento relativo y fijo se mantiene en flujo hasta que alcanza el umbral definido por top, entonces se pega.
- Para que funcione, el elemento sticky debe estar dentro de un contenedor cuyo overflow sea visible (no hidden, auto con clipping que impida el pegado).
- La propiedad top define la distancia desde la parte superior del viewport en la que el elemento se queda fijo (tener en cuenta cabeceras fijas y la barra de administración de WP).
Estructura HTML recomendada para el índice
Una estructura simple y semántica (sin utilizar etiquetas adicionales a las permitidas aquí) sería un título de índice y una lista de enlaces que apunten a los IDs de los encabezados del artículo. Ejemplo:
Índice
En WordPress, los enlaces deben apuntar a encabezados con IDs, por ejemplo lth2 id=introducciongt…lt/h2gt. En Gutenberg se pueden añadir «Anchor» a cada bloque de encabezado si trabajas con el editor clásico o quieres automatizarlo, verás más abajo una función PHP para generar IDs.
CSS básico para un TOC flotante usando position: sticky
Este bloque CSS cubre los puntos habituales: posición sticky, separación respecto a cabecera fija, altura máxima para permitir scroll interno y comportamiento responsive. Ajusta las variables según la altura real de tu cabecera.
/ Variables que puedes ajustar en tu tema /
:root {
--site-header-height: 72px / altura de la cabecera fija /
--toc-top-gap: 1rem / espacio adicional desde la cabecera /
}
/ Contenedor del índice /
.toc {
position: sticky
top: calc(var(--site-header-height) var(--toc-top-gap))
max-height: calc(100vh - (var(--site-header-height) var(--toc-top-gap) 1rem))
overflow: auto / permite desplazamiento interno si el índice es largo /
padding: 0.5rem
margin: 0
list-style: none
background: rgba(255,255,255,0.95)
border: 1px solid rgba(0,0,0,0.06)
border-radius: 6px
box-shadow: 0 2px 8px rgba(0,0,0,0.04)
}
/ Estilos de los enlaces dentro del TOC /
.toc a {
display: block
padding: 0.35rem 0.5rem
color: #0b66ff
text-decoration: none
border-radius: 4px
}
.toc a:focus,
.toc a:hover,
.toc a[aria-current=true] {
background: rgba(11,102,255,0.08)
outline: none
}
/ Responsive: en pantallas pequeñas, no forzar sticky (mejor integración con flujo) /
@media (max-width: 900px) {
.toc {
position: static
max-height: none
margin-bottom: 1rem
box-shadow: none
border: none
background: transparent
}
}
Notas sobre top y altura
- Si tu tema tiene cabecera fija (header) o un menú superior, ajusta –site-header-height para que el índice no quede oculto debajo de esa cabecera.
- Para la barra de administración de WordPress (cuando el usuario está logueado), añade unos píxeles extra a esa variable en tu CSS o establece la variable desde PHP según la condición de administrador.
Evitar errores comunes
- Si el elemento sticky parece no funcionar: revisa que ninguno de sus ancestros tenga overflow: hidden o transformaciones CSS que creen un nuevo contexto de formato (por ejemplo transform distinto de none). Sticky necesita un contenedor con overflow visible.
- Comprueba que el elemento .toc está dentro del mismo bloque donde esperas que se pegue (no fuera del contenedor principal si quieres que se mueva con la columna).
- La propiedad sticky no funciona en algunos navegadores antiguos sin embargo la mayoría de navegadores modernos la soportan. Considera una alternativa JS solo si necesitas compatibilidad extrema.
Generar IDs en los encabezados automáticamente (PHP)
Si no quieres añadir manualmente anchors a cada encabezado, puedes usar un filtro en functions.php que inserte IDs basados en el texto del encabezado. Aquí un ejemplo sencillo y robusto:
/ En functions.php: añadir IDs automáticos a h2/h3/h4 dentro del contenido /
function mi_tema_agregar_ids_a_encabezados( content ) {
// Solo en el contenido principal
if ( is_singular() in_the_loop() is_main_query() ) {
// Callback que añade id al encabezado si no lo tiene
content = preg_replace_callback(
/lt(h[2-4])([^gt]?)gt(.?)lt/1gt/i,
function( m ) {
tag = m[1]
attrs = m[2]
inner = m[3]
// Si ya tiene id, no cambiar
if ( preg_match(/sid=[]([^] )[]/, attrs) ) {
return m[0]
}
// Generar id a partir del texto (sanitizar)
id = sanitize_title( wp_strip_all_tags( inner ) )
// Si queda vacío, usar uno único
if ( empty( id ) ) {
id = heading- . wp_create_nonce( time() )
}
// Devolver el encabezado con el id
return <{tag}{attrs} id= . esc_attr( id ) . >{inner}{tag}>
},
content
)
}
return content
}
add_filter( the_content, mi_tema_agregar_ids_a_encabezados, 20 )
Este enfoque usa preg_replace_callback sobre the_content es simple y funciona bien para la mayoría de artículos. Si tu contenido contiene HTML complejo (shortcodes que generan encabezados, bloques dinámicos), prueba con cuidado. Para sitios con producción intensa, considera usar APIs de bloques o procesar los bloques con parse_blocks.
Cómo inyectar el índice automáticamente desde PHP
Puedes generar la lista de enlaces a partir de los encabezados detectados en el contenido y mostrarla en la sidebar o antes del artículo. Ejemplo simple que extrae IDs y textos y devuelve un UL:
function mi_tema_generar_toc( content ) {
matches = array()
preg_match_all( /lt(h[2-4])([^gt]?)id=[]([^] )[]([^gt]?)gt(.?)lt/1gt/i, content, matches, PREG_SET_ORDER )
if ( empty( matches ) ) {
return // sin encabezados con id
}
html = lth2gtÍndicelt/h2gtnltul class=toc aria-label=Índice de contenidosgtn
foreach ( matches as m ) {
id = esc_attr( m[3] )
title = wp_strip_all_tags( m[5] )
html .= ltligtlta href=#{id}gt . esc_html( title ) . lt/agtlt/ligtn
}
html .= lt/ulgtn
return html
}
/ Uso: echo mi_tema_generar_toc( get_the_content() ) /
Encolar estilos y scripts en WordPress (functions.php)
Incluye tu CSS en el tema de forma correcta con wp_enqueue_style. Si añades smooth scroll (opcional), encola un pequeño script. Ejemplo:
function mi_tema_enqueue_toc_assets() {
wp_enqueue_style( mi-toc-style, get_stylesheet_directory_uri() . /css/toc.css, array(), 1.0 )
// Smooth scroll opcional (código muy simple)
wp_enqueue_script( mi-toc-smooth, get_stylesheet_directory_uri() . /js/toc-smooth.js, array(), 1.0, true )
}
add_action( wp_enqueue_scripts, mi_tema_enqueue_toc_assets )
Ejemplo de JS para smooth scrolling (es opcional, la navegación funcionará sin JS):
document.addEventListener(click, function(e) {
if ( e.target.matches(.toc a) ) {
// Solo enlaces internos
var href = e.target.getAttribute(href)
if ( href href.charAt(0) === # ) {
var target = document.getElementById( href.substring(1) )
if ( target ) {
e.preventDefault()
target.scrollIntoView({ behavior: smooth, block: start })
history.replaceState(null, , href)
// marcar aria-current
document.querySelectorAll(.toc a).forEach(function(a){ a.removeAttribute(aria-current) })
e.target.setAttribute(aria-current, true)
}
}
}
})
Accesibilidad y usabilidad
- Añade aria-label a la lista para describir el propósito (aria-label=Índice de contenidos).
- Usa aria-current=true en el enlace correspondiente a la sección visible para ayudar a lectores de pantalla y usuarios de teclado.
- Asegúrate de estilos de foco visibles (outline o background) en a:focus y no esconderlos.
- Permite que el índice sea navegable por teclado evita interferir con tab order.
Consideraciones sobre la barra de administración y cabeceras fijas
Cuando el usuario está logueado, WordPress muestra la barra de administración (admin bar) en la parte superior además muchos temas usan cabeceras fijas. Para evitar que el TOC quede oculto, ajusta top en .toc para sumar la altura de ambos elementos. Una estrategia segura es exponer una variable CSS en tu tema (por ejemplo –site-header-height) y actualizar su valor desde PHP según condiciones (usuario logueado o no).
/ Ejemplo: imprimir una variable CSS en el head con la altura esperada /
function mi_tema_inline_header_height() {
header_height = 72 // valor por defecto en px
if ( is_admin_bar_showing() ) {
header_height = 32 // ajustar según la barra admin (aprox)
}
echo ltstylegt:root{ --site-header-height: {header_height}px }lt/stylegt
}
add_action( wp_head, mi_tema_inline_header_height )
Depuración y pruebas
- Prueba con distintos tamaños de pantalla (desktop, tablet, móvil).
- Activa el administrador (logueado) y verifica que el índice no quede oculto por la admin bar.
- Inspecciona ancestros del .toc en DevTools: si alguno tiene transform o overflow distinto de visible, position: sticky puede no comportarse.
- Verifica el comportamiento con lectores de pantalla (VoiceOver, NVDA) para comprobar que el índice es útil y claro.
Variantes y mejoras
- Resaltar la sección activa según scroll en vez de solo al hacer clic: usa IntersectionObserver en JS para detectar qué sección está en pantalla y actualizar aria-current.
- Hacer el TOC colapsable en pantallas pequeñas (un botón que despliegue la lista), útil para UX móvil.
- Agregar profundidad visual (indentar li según h2/h3/h4) para mejorar lectura de la estructura.
Ejemplo de IntersectionObserver (resumen)
// Observador para actualizar el enlace activo según sección visible
const headings = document.querySelectorAll(h2[id], h3[id], h4[id])
const links = document.querySelectorAll(.toc a)
const io = new IntersectionObserver((entries) =gt {
entries.forEach(entry =gt {
if (entry.isIntersecting) {
const id = entry.target.id
links.forEach(a =gt a.removeAttribute(aria-current))
const active = document.querySelector(.toc a[href=# id ])
if (active) active.setAttribute(aria-current, true)
}
})
}, { rootMargin: 0px 0px -60% 0px, threshold: 0 })
headings.forEach(h =gt io.observe(h))
Conclusión
Implementar un índice flotante con position: sticky en WordPress es una solución elegante y eficiente para mejorar la navegación en contenidos largos. Requiere tener en cuenta la estructura HTML (IDs en los encabezados), las limitaciones del contexto (overflow, transform), y ajustar el top para cabeceras o la barra admin. Combinado con pequeñas mejoras en PHP y JS obtendrás una experiencia accesible y robusta.
Leave a Reply