Introducción
Este tutorial explica paso a paso cómo crear un layout de documentación para un plugin de WordPress con un índice sticky (fijo) que permite navegación rápida por secciones, resaltado del elemento activo, scroll suave y un diseño responsivo y accesible. Incluye el esqueleto del plugin, la plantilla HTML generada por un shortcode, el CSS para el layout y el comportamiento sticky, y el JavaScript para scroll suave y scrollspy usando Intersection Observer. Además se tratan consideraciones de seguridad, rendimiento y mejoras posibles.
Estructura general y flujo
- Crear un plugin simple que registre y encole los assets (CSS/JS).
- Proveer un shortcode que genere el layout de documentación (índice contenido).
- Aplicar estilos para un índice fijo en pantallas grandes y colapsable en móviles.
- Implementar JS para: scroll suave, resaltar enlace activo, y accesibilidad (focus management).
- Considerar mejoras: generación automática de índice desde h2/h3, integración con Gutenberg, caché.
1) Estructura del plugin
Crea una carpeta en wp-content/plugins, por ejemplo doc-layout-sticky. Dentro, crea un archivo principal PHP y carpetas para css y js.
- doc-layout-sticky/
- doc-layout-sticky.php
- assets/
- css/doc-layout.css
- js/doc-layout.js
Archivo principal (encolado de assets y registro del shortcode)
Ejemplo mínimo del archivo principal del plugin:
lt?php
/
Plugin Name: Doc Layout Sticky
Description: Layout tipo documentación con índice sticky para plugins o páginas.
Version: 1.0
Author: Tu Nombre
Text Domain: doc-layout-sticky
/
if ( ! defined( ABSPATH ) ) {
exit
}
/ Encolar estilos y scripts /
function dls_enqueue_assets() {
wp_enqueue_style( dls-style, plugin_dir_url( __FILE__ ) . assets/css/doc-layout.css, array(), 1.0 )
wp_enqueue_script( dls-script, plugin_dir_url( __FILE__ ) . assets/js/doc-layout.js, array(), 1.0, true )
wp_localize_script( dls-script, dlsConfig, array(
scrollOffset => 80, // ajuste si hay un header fijo
) )
}
add_action( wp_enqueue_scripts, dls_enqueue_assets )
/ Shortcode que genera el layout /
function dls_docs_shortcode( atts, content = null ) {
// atributos por si quieres personalizar
atts = shortcode_atts( array(
title => ,
), atts, dls_docs )
// Aquí podrías generar el índice dinámicamente el ejemplo usa contenido estático
ob_start()
?>
lt!-- EJEMPLO: sustituir por contenido real o generar dinámicamente --gt
lth2gtIntroducciónlt/h2gt
ltpgtContenido de la introducción...lt/pgt
lth2gtInstalaciónlt/h2gt
ltpgtContenido de instalación...lt/pgt
lth2gtUsolt/h2gt
ltpgtContenido de uso...lt/pgt
lth2gtAPIlt/h2gt
ltpgtContenido de API...lt/pgt
lth2gtFAQlt/h2gt
ltpgtPreguntas frecuentes...lt/pgt
2) HTML y estructura semántica recomendada
El layout elemental consta de un contenedor con dos zonas: el índice (nav) y el contenido (main). Usa roles y atributos ARIA cuando haga falta para mejorar la accesibilidad.
Ejemplo de estructura generada por el shortcode (ya vista en el PHP): un nav con una lista de enlaces a #anchors y un main con secciones con id únicos.
3) CSS: layout, sticky y responsive
Consejos clave:
- Usar grid o flexbox para la distribución.
- En pantallas grandes el índice ocupa columna lateral y queda sticky con position: sticky.
- En móviles el índice queda colapsado (puede ser encima o ser un botón que despliegue).
- Controla offset cuando hay header fijo.
Ejemplo de CSS (archivo assets/css/doc-layout.css):
/ Estructura base /
.dls-wrapper {
display: grid
grid-template-columns: 280px 1fr
gap: 32px
align-items: start
}
/ Índice lateral /
.dls-toc {
position: relative
}
.dls-toc ul {
list-style: none
margin: 0
padding: 0
}
.dls-toc a {
display: block
padding: 8px 12px
color: #1a1a1a
text-decoration: none
border-radius: 4px
}
.dls-toc a:hover,
.dls-toc a:focus {
background: rgba(0,0,0,0.05)
outline: none
}
/ Sticky: posición con respect al contenedor principal /
.dls-toc {
position: sticky
top: 20px / ajustar si hay header fijo /
}
/ Enlace activo /
.dls-toc a.active {
background: #0073aa
color: #ffffff
font-weight: 600
}
/ Contenido /
.dls-content {
min-width: 0
}
.dls-content section {
margin-bottom: 40px
}
/ Responsive: en pantallas pequeñas apilamos /
@media (max-width: 900px) {
.dls-wrapper {
grid-template-columns: 1fr
}
.dls-toc {
position: relative
top: 0
margin-bottom: 16px
}
/ Opcional: hacer el TOC colapsable con un botón usando JS /
}
4) JavaScript: scroll suave y scrollspy con Intersection Observer
Usar Intersection Observer es más eficiente que escuchar scroll y calcular offsets constantemente. También implementaremos scroll suave y manejo de offset (para header fijo).
Ejemplo de JS (assets/js/doc-layout.js):
document.addEventListener(DOMContentLoaded, function () {
var toc = document.querySelector(.dls-toc)
if (!toc) return
var links = Array.prototype.slice.call(toc.querySelectorAll(a[href^=#]))
var sections = links.map(function (link) {
var id = link.getAttribute(href).slice(1)
return document.getElementById(id)
}).filter(Boolean)
var scrollOffset = (typeof dlsConfig !== undefined dlsConfig.scrollOffset) ? parseInt(dlsConfig.scrollOffset, 10) : 0
// Scroll suave
links.forEach(function (link) {
link.addEventListener(click, function (e) {
var targetId = this.getAttribute(href).slice(1)
var target = document.getElementById(targetId)
if (!target) return
e.preventDefault()
var targetTop = target.getBoundingClientRect().top window.pageYOffset - scrollOffset
window.scrollTo({ top: targetTop, behavior: smooth })
// Mover foco al heading por accesibilidad
var heading = target.querySelector(h1, h2, h3, h4, h5, h6)
if (heading) {
heading.setAttribute(tabindex, -1)
heading.focus()
// opcional: remover tabindex después
heading.addEventListener(blur, function () {
heading.removeAttribute(tabindex)
}, { once: true })
}
}, false)
})
// Intersection Observer para resaltar item activo
var observerOptions = {
root: null,
rootMargin: - Math.max( (scrollOffset - 10), 0 ) px 0px -40% 0px,
threshold: [0, 0.25, 0.5, 0.75, 1]
}
var activeLink = null
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
var id = entry.target.id
var newActive = toc.querySelector(a[href=# id ])
if (newActive newActive !== activeLink) {
if (activeLink) activeLink.classList.remove(active)
newActive.classList.add(active)
activeLink = newActive
}
}
})
}, observerOptions)
sections.forEach(function (section) {
observer.observe(section)
})
})
5) Accesibilidad y usabilidad
Aspectos a tener en cuenta:
- Usar roles y aria-label en el nav: ltnav aria-label=Índice de contenidogt.
- Asegurar que los enlaces sean focusables y que la navegación por teclado funcione correctamente.
- Cuando se hace scroll programático, mover el foco al heading objetivo para usuarios de lectores de pantalla.
- Proveer contraste suficiente para el enlace activo y el hover.
- Evitar que el índice se superponga al contenido en pantallas pequeñas usar un botón para abrir/cerrar si es necesario.
6) Generar el índice automáticamente (opcional pero recomendable)
Para no tener que escribir manualmente el índice, puedes parsear el contenido (o usar las cabeceras del post) y construir la lista. Aquí un enfoque sencillo en PHP para convertir los h2/h3 de un contenido en una lista de enlaces:
function dls_generate_toc_from_content( content ) {
dom = new DOMDocument()
libxml_use_internal_errors(true)
dom->loadHTML( . content )
libxml_clear_errors()
headings = dom->getElementsByTagName()
toc = array()
foreach ( headings as node ) {
tag = strtolower( node->nodeName )
if ( in_array( tag, array( h2, h3 ) ) ) {
text = trim( node->textContent )
id = node->getAttribute(id)
if ( ! id ) {
id = sanitize_title( text )
node->setAttribute( id, id )
}
toc[] = array( id => id, text => text, tag => tag )
}
}
// Devolver el contenido modificado y el TOC como array
return array( content => dom->saveHTML(), toc => toc )
}
Nota: cuando se usa DOMDocument con HTML generado por WordPress, hay que manejar encoding y errores. Otra opción es usar expresiones regulares simples, aunque menos seguras.
7) Seguridad y buenas prácticas en WordPress
- Escapa siempre valores que salgan en HTML (esc_attr, esc_html, wp_kses si permites HTML).
- No confíes en inputs del usuario: sanitiza atributos del shortcode (sanitize_text_field, intval, etc.).
- Registra scripts y estilos correctamente con wp_enqueue_ para evitar colisiones.
- Si generas HTML a partir del contenido, evita ejecutar código malicioso: usa funciones de saneamiento y whitelist de etiquetas cuando corresponda.
8) Variantes y mejoras
- Hacer el TOC colapsable en móviles: añadir un botón que alterne la clase .is-open y muestre/oculte el nav.
- Permitir anidado de enlaces (h2 > h3) visualmente indentado.
- Crear un bloque de Gutenberg personalizado para insertar el layout con controles visuales.
- Cachear la generación automática del TOC para evitar procesamiento en cada carga.
- Agregar enlaces copy link en cada heading para permitir copiar el anchor fácilmente.
9) Ejemplo de shortcode completo con TOC dinámico (esqueleto)
Patrón general: parsea el contenido o una plantilla, genera TOC, renderiza nav con enlaces y el contenido con ids. Aquí sólo se muestra la idea de integración (sustituye según tus necesidades):
function dls_docs_shortcode_dynamic( atts ) {
// Obtener contenido desde un archivo plantilla o desde post->post_content
content = file_get_contents( plugin_dir_path( __FILE__ ) . template/docs-sample.html )
parsed = dls_generate_toc_from_content( content )
content_with_ids = parsed[content]
toc_items = parsed[toc]
ob_start()
?>
10) Pruebas y depuración
- Probar en distintos navegadores y dispositivos (mobile/desktop).
- Comprobar que el índice no queda tapado por headers fijos.
- Verificar que los enlaces funcionan con teclados y lectores de pantalla.
- Usar Lighthouse o herramientas de accesibilidad para mejorar contraste y navegación.
Conclusión
Con los elementos mostrados puedes construir un sistema de documentación dentro de WordPress con un índice sticky funcional, accesible y responsivo. La versión básica es rápida de implementar: registra assets, genera el HTML (ya sea manual o dinámicamente), aplica CSS para layout y position: sticky, y añade un JS ligero con Intersection Observer para mejorar la experiencia. Desde ahí puedes evolucionar hacia un bloque de Gutenberg, generación automática más sofisticada, y opciones de personalización desde el admin.
Leave a Reply