¿Todavía no usas una Tabla de Contenido (ToC) en tus blogs de WordPress? La verdad es que te recomendaría mucho que lo hagas porque es un añadido que aporta un gran valor al usuario, cosa que también suele derivar en una mejor posición SEO del sitio. Además, es una función que queda muy bien en un blog.
En la mayoría de casos, la gente suele añadir uno de los múltiples plugins especializados precisamente en la creación de Tablas de Contenido. ¿Cuál es el problema de esto? Pues lo de siempre, estos plugins tienen que hacerse de modo que cumplan demasiadas posibilidades distintas y por ello, tienes muchísimo más código del que realmente necesitas.
¡Consulta nuestros servicios!
Por eso hoy te traigo esta solución que he estado usando en algunos proyectos. No solo te da control total, sino que además es más ligera que cualquier plugin que encuentres. Y ya sabes lo que pienso de añadir código innecesario.
TABLA DE CONTENIDOS
- Cómo Crear una Tabla de Contenidos en WordPress Sin Plugins
- Explicación del Código PHP para Tablas de Contenido Automáticas
- CSS Personalizado para Tablas de Contenido en WordPress
- Preguntas Frecuentes sobre Tablas de Contenido Personalizadas
- Medidas de Seguridad en la Implementación de ToC
- Ventajas de Usar Código Personalizado vs Plugins para ToC
- ¿Te gustaría implementar esto en tu web sin complicaciones?
Cómo Crear una Tabla de Contenidos en WordPress Sin Plugins
Lo primero es lo primero. Añade este código a tu functions.php o, si lo prefieres (y te lo recomiendo), como snippet en Code Snippets.
<?php
function tabla_contenidos_automatica($contenido) {
if (!is_singular('post') || !in_the_loop() || !is_main_query()) {
return $contenido;
}
if (strpos($contenido, '<h2') === false) {
return $contenido;
}
preg_match_all('/<h2[^>]*>(.*?)<\/h2>/', $contenido, $coincidencias);
if (empty($coincidencias[1]) || count($coincidencias[1]) < 2) {
return $contenido;
}
$tabla_contenidos = '<div class="tabla-contenidos">';
$tabla_contenidos .= '<p><strong>TABLA DE CONTENIDOS</strong></p>';
$tabla_contenidos .= '<ul>';
$reemplazos = [];
foreach ($coincidencias[1] as $indice => $texto_encabezado) {
$texto_limpio = wp_strip_all_tags($texto_encabezado);
if (empty(trim($texto_limpio))) {
continue;
}
$id = 'toc-' . sanitize_title($texto_limpio) . '-' . $indice;
$enlace = '#' . $id;
$tabla_contenidos .= '<li>';
$tabla_contenidos .= '<a href="' . esc_url($enlace) . '">';
$tabla_contenidos .= esc_html($texto_limpio);
$tabla_contenidos .= '</a></li>';
$reemplazos[] = [
'original' => $coincidencias[0][$indice],
'modificado' => '<h2 id="' . esc_attr($id) . '">' . $texto_encabezado . '</h2>',
];
}
$tabla_contenidos .= '</ul>';
$tabla_contenidos .= '</div>';
if (!empty($reemplazos)) {
$patron_primer_h2 = '/<h2[^>]*>/';
if (preg_match($patron_primer_h2, $contenido, $primer_coincidencia, PREG_OFFSET_CAPTURE)) {
$posicion = $primer_coincidencia[0][1];
$contenido = substr_replace($contenido, $tabla_contenidos, $posicion, 0);
}
foreach ($reemplazos as $reemplazo) {
$contenido = str_replace($reemplazo['original'], $reemplazo['modificado'], $contenido);
}
}
return $contenido;
}
add_filter('the_content', 'tabla_contenidos_automatica', 9);
?>
Ahora vamos a ver qué hace exactamente cada parte, porque no es magia (aunque lo parezca).
Explicación del Código PHP para Tablas de Contenido Automáticas
La función es un filtro de WordPress bastante estándar, pero tiene algunos detalles interesantes. Lo primero que hace es asegurarse de que estamos en el contexto correcto.
Validación del Contexto en WordPress
Mira tres cosas básicas:
is_singular('post'): ¿Es un post individual? Si no, fuera.in_the_loop(): ¿Estamos en el loop principal? Importante para no meter la tabla en widgets.is_main_query(): ¿Es la consulta principal? Más de lo mismo.
Estas comprobaciones evitan que la tabla aparezca donde no debe, como en páginas, listados o widgets. Básicamente, nos aseguramos de no romper nada.
Detección de Encabezados H2 en el Contenido
Con strpos() (string position) miramos si hay algún H2 en el contenido. Si no hay, devolvemos el contenido tal cual. ¿Para qué crear una tabla vacía?
Si hay H2, usamos preg_match_all() para capturarlos todos. La expresión regular /<h2[^>]*>(.*?)<\/h2>/ busca cada etiqueta H2 y su contenido.
Aquí hay un detalle: si hay menos de dos H2, tampoco creamos tabla. Una tabla con un solo elemento no tiene sentido, la verdad.
Generación de IDs Únicos para Navegación Interna
Para cada H2 que encontramos hacemos esto:
- Limpiamos el texto:
wp_strip_all_tags()se carga cualquier HTML dentro del H2. - ID único:
sanitize_title()crea un identificador válido para URLs, más un número por si hay H2 repetidos. - Enlace: Creamos un
<li>con un enlace al ID. - Preparamos el cambio: Guardamos el H2 original y el nuevo con ID.
El uso de sanitize_title() aquí es clave porque asegura IDs válidos.
Inserción Precisa de la Tabla de Contenidos
Aquí está el truco interesante. Con preg_match() y PREG_OFFSET_CAPTURE encontramos exactamente dónde está el primer H2. No solo el texto, sino su posición en caracteres.
Luego usamos substr_replace() para meter la tabla justo ahí. ¿Por qué substr_replace() y no otra cosa? Porque es más eficiente cuando ya sabes la posición exacta.
Finalmente, cambiamos todos los H2 originales por los que llevan ID con str_replace(). Y listo, los enlaces funcionan.
CSS Personalizado para Tablas de Contenido en WordPress
El PHP solo genera el HTML. Si quieres que tenga buen aspecto, necesitas CSS. Te dejo unos estilos básicos que puedes ajustar a tu tema.
.tabla-contenidos {
background: #f8f9fa;
border-left: 4px solid #0073aa;
padding: 20px;
margin: 25px 0;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.tabla-contenidos > p {
margin-top: 0;
margin-bottom: 15px;
color: #2c3e50;
font-size: 1.1em;
}
.tabla-contenidos > ul {
margin: 0;
padding-left: 20px;
list-style-type: none;
}
.tabla-contenidos > ul > li {
margin-bottom: 10px;
position: relative;
padding-left: 15px;
}
.tabla-contenidos > ul > li::before {
content: "•";
color: #0073aa;
font-size: 1.5em;
position: absolute;
left: 0;
top: -2px;
}
.tabla-contenidos > ul > li > a {
text-decoration: none;
color: #3498db;
font-weight: 500;
transition: color 0.2s ease;
display: block;
padding: 3px 0;
}
.tabla-contenidos > ul > li > a:hover {
color: #2980b9;
text-decoration: underline;
}
@media (max-width: 768px) {
.tabla-contenidos {
padding: 15px;
margin: 20px 0;
}
}
Estos estilos son bastante neutrales. Si usas un framework como Bootstrap o tu tema tiene estilos muy específicos, probablemente quieras ajustar los márgenes y colores.
Preguntas Frecuentes sobre Tablas de Contenido Personalizadas
¿Cómo Incluir Encabezados H3 y H4 en la Tabla de Contenidos?
Ahora mismo solo funciona con H2. Para incluir H3, cambia la expresión regular para que busque <h3> en lugar de (o además de) <h2>. Pero ojo, si incluyes demasiados niveles la tabla puede quedar muy larga.
Compatibilidad con HTML dentro de los Encabezados
Sí, wp_strip_all_tags() se lo quita todo. Así evitamos que salgan etiquetas raras en la tabla.
Solución para Encabezados H2 Duplicados
El código añade un número al ID. Primer «Conclusión»: toc-conclusion-0. Segundo «Conclusión»: toc-conclusion-1. Cada enlace apunta a su sección correspondiente.
Personalización de la Posición de la Tabla de Contenidos
Por defecto va antes del primer H2. Puedes modificar la lógica de inserción si quieres otro comportamiento. O hacer un shortcode para colocarla manualmente.
Medidas de Seguridad en la Implementación de ToC
He incluido varias medidas de seguridad: Esto es importante sobre todo si aceptas contenido de usuarios.
esc_html(): Para el texto que mostramos.esc_url(): Para los enlaces.esc_attr(): Para los IDs.sanitize_title(): Para crear IDs seguros.
Con esto evitamos problemas como XSS o inyecciones de código.
Ventajas de Usar Código Personalizado vs Plugins para ToC
Esta solución es más ligera que cualquier plugin y te da control total. Además, aprendes cómo funcionan los filtros de WordPress por dentro, que siempre viene bien.
Como siempre, prueba primero en un entorno de staging o local. Si algo no funciona, revisa que tus H2 estén bien formados y que no haya conflictos con otros plugins o código de tu tema.
¿Te ha quedado alguna duda? Pues ya sabes, a los comentarios.
¿Te gustaría implementar esto en tu web sin complicaciones?
Ofrezco un servicio de implementación técnica express: por 50€ te instalo y configuro esta solución en tu WordPress, con garantía de funcionamiento y entrega en 24–48 horas.
Más información aquí.