Diseño de documentación sin frameworks: barra lateral fija, contenido y TOC derecho con CSS Grid
Tu página de documentación se ve bien en una captura de pantalla. Luego llega a producción: una barra lateral sticky que no se queda fija, un TOC que tiembla y una columna de contenido que se estira como chicle o se comprime hasta parecer una nota rescatada.
Esta es la guía de campo para construir un diseño de documentación de tres columnas con CSS Grid—navegación izquierda, contenido central, TOC a la derecha—sin framework y sin el acostumbrado espiral de “por qué el sticky me odia”.
Tabla de contenidos
- Por qué este diseño falla en producción
- El objetivo: tres columnas que se comportan
- Núcleo de CSS Grid: la plantilla que funciona
- Reglas de sticky: qué lo hace fijarse (y qué lo rompe)
- Contenedores de desplazamiento: el asesino silencioso del sticky
- Anchura legible de contenido y restricciones tipográficas
- TOC derecho: marcado, offsets y estados activos
- Comportamiento responsive sin framework
- Accesibilidad: enlaces de salto, foco, reducción de movimiento
- Rendimiento y estabilidad: la thrash de layout también es una caída
- Tareas prácticas con comandos (y cómo decidir)
- Guía rápida de diagnóstico
- Errores comunes (síntoma → causa raíz → solución)
- Listas de verificación / plan paso a paso
- Tres minihistorias corporativas desde el terreno
- Hechos y contexto histórico
- Preguntas frecuentes
- Próximos pasos que realmente ayudan
Por qué este diseño falla en producción
Las páginas de documentación de tres columnas fallan por razones aburridas, y por eso fallan tan a menudo. No porque CSS Grid no pueda hacerlo—Grid lo hace muy bien—sino porque la página alrededor del grid (cabeceras, contenedores de scroll, reglas de overflow, banners inyectados, avisos de cookies y “envoltorios” útiles) cambia silenciosamente las reglas de las que depende el posicionamiento sticky.
Cuando un SRE dice “funciona en staging”, normalmente quiere decir “funciona en mi portátil con un viewport y sin scripts de terceros”. Las páginas de docs son peores: píxeles de marketing, widgets de feedback y resaltadores de sintaxis se unen a la fiesta. Cada uno puede crear un contenedor de desplazamiento o forzar relayout en tiempo de scroll. Así aparecen los síntomas clásicos:
- Barra de navegación sticky que deja de pegarse a mitad de camino.
- TOC derecho que se superpone al pie de página o desaparece detrás de él.
- Columna de contenido que desborda horizontalmente por líneas largas de código.
- Scroll entrecortado cuando un script recalcula el estado activo del TOC con demasiada frecuencia.
El enfoque correcto es tratar el layout como infraestructura de producción: dueños claros, pocas piezas móviles, comportamiento medible y un modelo de fallo conocido.
El objetivo: tres columnas que se comportan
Queremos un diseño con:
- Barra lateral izquierda (navegación del sitio), fija bajo una cabecera superior, desplazable de forma independiente cuando sea larga.
- Contenido principal con una longitud de línea cómoda y bloques de código resilientes.
- TOC derecho (encabezados de la página), fijo, desplazable, que no robe el foco y que no requiera un framework.
Y queremos que degrade con gracia: en viewports pequeños, colapsar a una sola columna sin convertirse en un laberinto de barras de desplazamiento anidadas.
height: 100vh + scroll interno para páginas de docs. Rompe el sticky y complica los anclajes.
Núcleo de CSS Grid: la plantilla que funciona
Hay muchas formas de hacerlo. La mayoría son frágiles. El patrón fiable es: dejar que el body (o el documento) posea el desplazamiento, usar Grid para las columnas y usar position: sticky para los dos paneles laterales. Luego restringir las anchuras con columnas fijas para las barras laterales y una columna central flexible con minmax(0, 1fr).
Ese minmax(0, 1fr) no es decorativo. Sin el 0, la columna central puede negarse a encogerse debido al dimensionado intrínseco, y obtienes overflow. Este es el problema del tipo “¿por qué mi grid ignora mi ancho?”.
Forma del grid: [sidebar] [content] [toc]
Plantilla: grid-template-columns: 270px minmax(0, 1fr) 250px;
Mantén las barras laterales como elementos normales en el flujo del grid; no las posiciones de forma absoluta. La posición absoluta parece ingeniosa hasta que intentas imprimir, manejar alturas dinámicas de cabecera o soportar safe areas en móviles.
Estructura HTML de referencia
Mínima, aburrida, estable:
cr0x@server:~$ cat layout.html
<header>...sticky top bar...</header>
<div class="layout">
<nav>...left nav...</nav>
<main>...content...</main>
<aside>...right toc...</aside>
</div>
El “secreto” es que nada aquí crea un nuevo contexto de desplazamiento. Así es como el sticky sobrevive al contacto con el enemigo.
Reglas de sticky: qué lo hace fijarse (y qué lo rompe)
position: sticky es simple hasta que deja de serlo. Sticky funciona relativo al ancestro con desplazamiento más cercano. Si un ancestro tiene overflow establecido a algo que crea un contenedor de scroll (auto, scroll, hidden en la práctica), sticky se vuelve relativo a ese contenedor en lugar del viewport. A veces eso es lo que quieres. La mayoría de las veces en docs, no.
Esta es la configuración estable:
- Desplazamiento del documento (sin “scroll interno de la app”).
- Barras laterales sticky con
top: headerHeight + gap. - Las barras laterales reciben
max-heightyoverflow: autopara que solo ellas se desplacen cuando sean demasiado largas.
Y aquí están los asesinos clásicos del sticky:
- Un ancestro con overflow: un div envoltorio con
overflow: hiddenpara recortar sombras, un gestor de modales o “arreglar” overflow horizontal. - Transforms en un ancestro:
transformy algunos patrones defilter/will-changepueden afectar los bloques contenedores y el comportamiento de scroll de maneras sorprendentes. - Usar
height: 100vhcon scroll interno: el sticky se vuelve relativo al contenedor de scroll interno y los enlaces ancla no aterrizan donde esperas.
Offsets de la cabecera: no adivines
Si tienes una cabecera sticky, los saltos de ancla quedarán debajo de ella. Establece scroll-margin-top en los encabezados y asunto resuelto. No lo arregles con divs vacíos de compensación.
Ejemplo:
h2, h3 { scroll-margin-top: calc(var(--header-h) + 12px); }
Contenedores de desplazamiento: el asesino silencioso del sticky
La mayoría de los bugs de sticky no son bugs de sticky. Son bugs de contenedores de desplazamiento. Un contenedor de scroll se crea cuando un elemento recorta overflow y tiene una región de overflow desplazable. En bases de código reales, los contenedores de scroll aparecen porque alguien quería:
- evitar el desplazamiento horizontal por bloques de código (
overflow-x: hiddenen un wrapper) - aplicar un desenfoque o sombra y recortarlo
- una “caja de aplicación de altura completa” para que el pie no se mueva
- un envoltorio de virtualización para resultados de búsqueda o un widget de feedback
Tu trabajo es identificar el ancestro con desplazamiento más cercano al elemento sticky. En Chrome DevTools puedes verlo a simple vista, pero en producción quieres comprobaciones repetibles. Incluso puedes añadir un modo CSS de depuración que marque los elementos con propiedades overflow. Hazlo un toggle en tiempo de compilación.
Desplazamiento anidado: elige exactamente un ganador
Las páginas de docs deberían tener exactamente un desplazamiento principal: el documento. Las barras laterales pueden ser regiones de desplazamiento secundarias cuando sea necesario, pero la página no debería depender de una región de desplazamiento interna para la navegación básica.
El desplazamiento anidado rompe:
- la restauración de scroll al navegar atrás/adelante del navegador
- los enlaces ancla
- el comportamiento de teclado PageDown
- algunas expectativas de lectores de pantalla
Un chiste corto #1: Los contenedores de desplazamiento anidados son el equivalente de UI de RAID 0: impresionantemente rápidos para convertir pequeños errores en grandes consecuencias.
Anchura legible de contenido y restricciones tipográficas
La columna central es donde viven tus lectores. Una pantalla ancha no significa que debas usarla. Mantén la anchura del contenido en caracteres, no en píxeles. Es un sitio de docs, no una valla publicitaria.
Reglas que funcionan:
- Establece
max-widthen el flujo del contenido, por ejemplo,74cha80ch. - Permite que los bloques de código se desplacen horizontalmente dentro de sí mismos. No “arregles” el overflow ocultándolo en wrappers externos.
- Usa
min-width: 0(o elminmax(0, 1fr)de Grid) para que la columna central pueda encogerse.
Además: los bloques de código son la carga de trabajo de almacenamiento de los docs. Son picos, impredecibles y llenos de casos patológicos como cadenas sin rupturas. Trátalos como una prueba de rendimiento: constrúyelos, aíslalos y mídelo.
TOC derecho: marcado, offsets y estados activos
Un TOC a la derecha es útil cuando está tranquilo. No debería convertirse en un segundo sistema de navegación que compita con tu navegación principal. Manténlo minimalista:
- Incluye solo H2/H3. Omite H4 a menos que escribas documentos estándares.
- Trunca visualmente encabezados largos pero conserva el texto completo para lectores de pantalla usando
aria-labelsi es necesario. - No expandas automáticamente un árbol mientras haces scroll a menos que puedas hacerlo sin thrash de layout.
Objetivos de anclaje: IDs estables, encabezados estables
No generes IDs en tiempo de ejecución en el cliente si puedes evitarlo. Rompe enlaces entrantes y hace diffs ruidosos. Genera IDs en tiempo de compilación (generador de sitios estáticos, procesador de markdown o incluso un pequeño script en tu pipeline). Si realmente no tienes paso de build, aún puedes usar reglas deterministas, pero entonces tus encabezados deben ser estables.
Resaltado de sección activa: IntersectionObserver, no matemáticas de scroll
Los scripts antiguos de TOC usaban listeners de scroll y comprobaciones manuales de rectángulos. Funciona hasta que deja de funcionar. El primitivo correcto es IntersectionObserver, diseñado para preguntas del tipo “¿está este encabezado en vista?”.
Dicho esto: puedes prescindir por completo del resaltado activo. Muchos equipos lo implementan y luego pasan meses ajustándolo. Si tienes tiempo limitado, inviértelo en sticky y tipografía. Los lectores perdonan un TOC pasivo. No perdonan un TOC que cause jank en el scroll.
Comportamiento responsive sin framework
En viewports estrechos, un diseño de tres columnas se vuelve autoparódico. Colapsa a una columna. Puedes:
- apilar nav, contenido, toc (en ese orden), o
- ocultar el TOC derecho y proporcionar un mini-TOC dentro del contenido cerca de la parte superior
La segunda opción suele ser mejor: menos regiones de desplazamiento, menos ruido, menos toques erróneos.
Usa un breakpoint alrededor de donde la columna de contenido se estrecha—típicamente cerca de 1024px según los anchos de las barras laterales. Más allá de ese punto, trata la vista como “móvil” y simplifica. No hagas “dos columnas con un TOC flotante” a menos que disfrutes depurar casos límite en iOS Safari.
Accesibilidad: enlaces de salto, foco, reducción de movimiento
Los diseños de docs son ricos en navegación. Si construyes una carcasa sticky elegante y te olvidas de los usuarios de teclado, has enviado un laberinto con las luces apagadas.
Enlaces de salto y roles de referencia
Proporciona un enlace “Saltar al contenido” como el primer elemento enfocables. Usa elementos semánticos (<nav>, <main>, <aside>) y etiquétalos con aria-label donde sea útil.
Comportamiento del foco y desplazamiento
Cuando un usuario navega con tab por una barra lateral sticky, los contornos de foco deben permanecer visibles y no ser recortados por overflow. Si tu nav es desplazable (overflow: auto), asegúrate de que los elementos enfocados se desplacen a la vista. Los navegadores generalmente manejan esto, pero puede fallar con gestión de foco personalizada o si aplicas transforms extraños.
Reducción de movimiento
Si añades desplazamiento suave, respeta prefers-reduced-motion. También considera que el desplazamiento suave puede sentirse “con retardo” en dispositivos de gama baja. La fiabilidad supera el ambiente.
Rendimiento y estabilidad: la thrash de layout también es una caída
Las páginas de docs no deberían freír portátiles. Y aun así lo hacen, principalmente debido a:
- manejadores de scroll que realizan trabajo pesado
- scripts de TOC midiendo layout repetidamente (
getBoundingClientRect) por frame - resaltado de sintaxis que se ejecuta de nuevo en cambios de ruta en SPAs
- fuentes web que causan reflujo tardío que desplaza anclajes
Dos reglas estrictas:
- No ejecutes lecturas y escrituras de layout en el mismo frame en respuesta al scroll. Si debes hacerlo, agrúpalas.
- Prefiere primitivos del navegador (sticky en CSS, IntersectionObserver) sobre sondeos personalizados.
«La esperanza no es una estrategia.» — Gene Kranz
En términos de operaciones: trata el rendimiento del scroll como un presupuesto. Si tu diseño de docs provoca tareas largas o recálculos de layout repetidos, aparecerá en el monitoreo de usuarios reales como “lentitud misteriosa”, que es lo peor porque es difícil de reproducir.
Tareas prácticas con comandos (y cómo decidir)
Estas tareas asumen que tienes un entorno local, un artefacto de compilación (incluso HTML escrito a mano) y al menos un servidor estático. Los comandos son del tipo que realmente ejecutas al depurar comportamiento de layout entre entornos. Cada tarea incluye: el comando, qué significa la salida y la decisión que tomas.
Tarea 1: Servir el sitio localmente con caché deshabilitada
cr0x@server:~$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
Qué significa: Estás sirviendo archivos estáticos sin trucos de service worker. Buena línea base.
Decisión: Reproduce el bug del sticky aquí primero. Si solo ocurre detrás de tu CDN/proxy real, buscas marcado/scripts inyectados o cabeceras/CSP diferentes.
Tarea 2: Verificar compresión y tipo de contenido (los scripts TOC fallan con MIME incorrecto)
cr0x@server:~$ curl -I http://127.0.0.1:8080/index.html
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.11.2
Content-type: text/html
Content-Length: 24891
Last-Modified: Sat, 28 Dec 2025 10:11:41 GMT
Qué significa: MIME correcto y Last-Modified estable. Algunos navegadores se comportan raro si CSS o JS se sirven como text/plain.
Decisión: Si el MIME está mal en producción, arregla la configuración del servidor antes de depurar CSS. Si no, estarás depurando fantasmas.
Tarea 3: Confirmar que las columnas del grid se computaron como esperabas
cr0x@server:~$ node -e "console.log('Use DevTools: Elements → Computed → grid-template-columns')"
Use DevTools: Elements → Computed → grid-template-columns
Qué significa: No hay CLI que supere a DevTools para pistas de grid computadas. Estás comprobando overrides no deseados.
Decisión: Si ves algo como 270px 1000px 250px cuando esperabas que el medio fuera flexible, encuentra la regla que eliminó minmax(0, 1fr) o introdujo comportamiento min-content.
Tarea 4: Buscar reglas de overflow que creen contenedores de scroll
cr0x@server:~$ rg -n "overflow\s*:\s*(auto|scroll|hidden)" ./styles
styles/app.css:41: overflow: hidden;
styles/layout.css:112: overflow: auto;
Qué significa: Encontraste posibles asesinos del sticky. overflow: hidden en un ancestro es un sospechoso principal.
Decisión: Para cada coincidencia, verifica si envuelve tus elementos sticky. Si sí, elimínala o muévela a un ámbito más estrecho (por ejemplo, solo el bloque de código, no todo el wrapper de layout).
Tarea 5: Identificar wrappers inesperados del tooling de build
cr0x@server:~$ rg -n "layout|wrapper|container|shell" ./dist/index.html
52:<div class="app-shell">
53: <div class="page-wrapper">
Qué significa: Tu “página simple” está dentro de un app shell. Ese shell suele poseer el desplazamiento.
Decisión: Si .app-shell usa height: 100vh + overflow: auto, o refactorizas el shell o aceptas que el sticky se fijará dentro de ese contenedor y ajustas expectativas (anclas, restauración de scroll, etc.). Recomiendo refactorizar para docs.
Tarea 6: Comprobar si algún padre usa transforms (puede romper suposiciones de fixed/sticky)
cr0x@server:~$ rg -n "transform\s*:" ./styles
styles/marketing.css:88: transform: translateZ(0);
styles/marketing.css:212: transform: scale(1.02);
Qué significa: Alguien aplicó hacks de transform para “suavidad”. A menudo culto de carga.
Decisión: Si esos elementos transformados envuelven el layout, elimina el transform o muévelo a un hijo. Luego vuelve a probar el sticky. No mantengas hacks GPU a menos que puedas justificarlos con resultados medidos.
Tarea 7: Confirmar que la altura de la cabecera es la que tu offset sticky espera
cr0x@server:~$ rg -n "--header-h" ./styles
styles/app.css:17: --header-h: 56px;
Qué significa: Usas una variable CSS para la altura de la cabecera. Bien. Ahora verifica que la cabecera realmente mida 56px en los distintos breakpoints.
Decisión: Si la cabecera se envuelve en pantallas pequeñas y se vuelve más alta, debes actualizar --header-h de forma responsiva o evitar depender de un valor fijo en píxeles (por ejemplo, calcular con el layout o usar top con un valor menor y dar a los encabezados un scroll-margin-top que compense de más).
Tarea 8: Capturar overflow horizontal introducido por bloques de código
cr0x@server:~$ rg -n "pre\s*\{|code\s*\{|white-space|word-break|overflow-x" ./styles
styles/code.css:9: pre { overflow-x: auto; }
styles/code.css:10: pre { white-space: pre; }
Qué significa: Tus bloques de código se desplazarán internamente, lo cual es correcto. Si ves overflow-x: hidden en wrappers externos, eso es una mala señal.
Decisión: Mantén el control de overflow al nivel del bloque de código. Si el wrapper del layout oculta overflow, sticky puede romper y los contornos de foco pueden cortarse.
Tarea 9: Validar que los anclajes existen y son únicos
cr0x@server:~$ rg -n "id=\"" ./dist/index.html | head
118:<h2 id="grid-core">CSS Grid core: the one template that works</h2>
156:<h2 id="sticky-rules">Sticky rules: what makes it stick (and what kills it)</h2>
Qué significa: Los IDs existen. Ahora confirma la unicidad.
Decisión: Si los IDs se repiten, los navegadores saltarán al primer match, tu TOC parecerá “equivocado” y depurar será molesto. Corrígelo en la generación.
Tarea 10: Asegurar que la CSP no bloquea tu script de TOC (común en entornos corporativos)
cr0x@server:~$ curl -I https://docs.example.internal/ | rg -i "content-security-policy"
Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'self'
Qué significa: Los scripts inline pueden estar bloqueados. Si tu TOC depende de JS inline, no se ejecutará.
Decisión: Mueve scripts a archivos estáticos servidos desde 'self', o añade nonces/hashes. Para docs, prefiere TOC sin JS y mejora opcionalmente.
Tarea 11: Detectar tareas largas del cliente que se correlacionan con el scroll
cr0x@server:~$ google-chrome --enable-logging=stderr --v=1 2>&1 | head
[1228/101521.402:VERBOSE1:chrome_main_delegate.cc(764)] basic startup complete
[1228/101521.409:VERBOSE1:startup_browser_creator.cc(157)] Launched chrome
Qué significa: Esto es solo logging de arranque; el trabajo real está en las grabaciones de Performance de DevTools. Aun así, lanzar con logs ayuda cuando se sospechan problemas de GPU/compositor.
Decisión: Si las grabaciones de performance muestran recálculos de layout repetidos durante el scroll, audita los scripts del TOC y cualquier listener de scroll. Reemplázalos por IntersectionObserver o limita la frecuencia a intervalos razonables.
Tarea 12: Confirmar que ningún service worker está cacheando CSS/JS rotos
cr0x@server:~$ rg -n "serviceWorker|navigator\.serviceWorker" ./dist
dist/app.js:14:navigator.serviceWorker.register("/sw.js")
Qué significa: Existe un service worker. Puede fijar CSS antiguo causando “está arreglado pero no se arregló”.
Decisión: Para docs, considera evitar service workers a menos que necesites offline. Si lo mantienes, implementa cache busting y un flujo de actualización claro.
Tarea 13: Revisar cabeceras HTTP de caché para CSS (CSS obsoleto causa regresiones fantasma)
cr0x@server:~$ curl -I https://docs.example.internal/assets/app.css | rg -i "cache-control|etag|last-modified"
Cache-Control: public, max-age=31536000, immutable
ETag: "a8b3c-5f1c2d9b"
Qué significa: Caché inmutable está bien solo si los nombres de archivo tienen hash de contenido.
Decisión: Si el nombre de archivo es estático (por ejemplo, app.css), quita caché inmutable o añade hashing. Si no, las correcciones de layout no llegarán a los usuarios.
Tarea 14: Validar que tu HTML tiene un único landmark main
cr0x@server:~$ tidy -errors -q ./dist/index.html | head
line 1 column 1 - Warning: missing
declaration
line 54 column 5 - Warning: should not appear as a child of
Qué significa: Problemas de estructura HTML pueden confundir tecnología asistiva y a veces tus selectores CSS. (También: añade el doctype.)
Decisión: Corrige advertencias de estructura que afecten landmarks y encabezados. No persigas advertencias cosméticas a menos que causen problemas reales.
Tarea 15: Auditar la jerarquía de encabezados (la calidad del TOC depende de ello)
cr0x@server:~$ rg -n "
Qué significa: Los encabezados existen y parecen ordenados. Si ves h4 antes que h2, tu generador de TOC hará cosas raras.
Decisión: Impone orden de encabezados en las guías de autoría y en checks de CI. La disciplina de contenido paga dividendos.
Guía rápida de diagnóstico
Cuando el diseño está roto y alguien pregunta “¿es un bug de CSS?”, no tienes tiempo para sensaciones. Necesitas una guía corta que encuentre el cuello de botella rápido.
Primero: confirma quién posee el desplazamiento
- Comprobar: ¿la página se desplaza en
body/html, o hay un contenedor interno con su propia barra de desplazamiento? - Por qué: sticky se comporta relativo al contenedor de desplazamiento más cercano.
- Acción: si hay un contenedor de desplazamiento interno, decide quitarlo (preferido para docs) o ajustar offsets sticky y de anclas en consecuencia.
Segundo: encuentra el ancestro con overflow/transform más cercano
- Comprobar: inspecciona la barra lateral y el TOC; sube por el DOM buscando
overflow,transform,filter,contain. - Por qué: cualquiera de estos puede cambiar el contexto contenedor del sticky o recortarlo.
- Acción: elimina o reduce el alcance; no apliques overflow a lo loco en wrappers de layout.
Tercero: verifica sizing de tracks de grid y comportamiento de min-width
- Comprobar: asegúrate de que la columna central sea
minmax(0, 1fr)y que el contenido o sus hijos no tenganmin-widthque fuerce overflow. - Por qué: el dimensionado intrínseco de bloques de código y cadenas largas destruirá tu layout.
- Acción: aplica
min-width: 0en hijos del grid según sea necesario y aísla overflow a bloques de código.
Cuarto: mide el jank de desplazamiento
- Comprobar: graba un trace de Performance mientras haces scroll.
- Por qué: los scripts del TOC son culpables frecuentes.
- Acción: reemplaza manejadores de scroll por IntersectionObserver o elimina el resaltado activo si no puedes hacerlo barato.
Errores comunes (síntoma → causa raíz → solución)
La barra lateral sticky no se queda fija
Síntoma: La barra lateral se desplaza como un elemento normal.
Causa raíz: Falta top en el elemento sticky, o el sticky está aplicado al nodo equivocado (por ejemplo, la lista interna en lugar del panel).
Solución: Aplica position: sticky y top: ... al contenedor de la barra lateral. Asegúrate de que no esté dentro de un ancestro transformado.
Sticky funciona hasta cierto punto y luego deja de hacerlo
Síntoma: La barra lateral se pega al principio y luego “suelta”.
Causa raíz: El elemento sticky está limitado por la altura de su bloque contenedor (a menudo porque la fila del grid o el wrapper termina antes de lo que crees).
Solución: Mantén el elemento sticky en el mismo contexto de desplazamiento que el documento. Evita wrappers con overflow que recorten. Asegura que el contenedor del grid abarque toda la altura del contenido (suele ocurrir naturalmente si no fuerzas alturas).
El TOC derecho se superpone al pie o al final de la página
Síntoma: El panel del TOC se sitúa encima del contenido del pie de página.
Causa raíz: Usar position: fixed en vez de sticky, o un wrapper de layout que no reserva espacio para el TOC.
Solución: Usa sticky dentro de la celda del grid para que termine naturalmente cuando el contenido termine. Evita fixed salvo que quieras realmente un panel flotante.
La columna de contenido provoca desplazamiento horizontal en toda la página
Síntoma: Puedes desplazar la página horizontalmente. Todos lo odian.
Causa raíz: Líneas largas de código o cadenas sin rupturas, más un track del grid que no quiere encogerse porque la columna central es 1fr sin minmax(0, ...).
Solución: Usa minmax(0, 1fr). Pon overflow-x: auto en pre. No pongas overflow-x: hidden en wrappers externos como “solución”.
Los anclajes saltan debajo de la cabecera
Síntoma: Al hacer clic en el TOC aterrizas con el encabezado oculto bajo la cabecera sticky.
Causa raíz: No hay scroll-margin-top en los encabezados.
Solución: Añade scroll-margin-top con la altura de la cabecera + separación. Manténlo en CSS, no en JavaScript.
El resaltado activo del TOC va por detrás o parpadea
Síntoma: El resaltado salta entre encabezados o se actualiza tarde.
Causa raíz: Listener de scroll haciendo demasiado trabajo, o umbrales mal ajustados; también común con alturas de cabecera variables.
Solución: Usa IntersectionObserver y elige un root margin sensato (por ejemplo, un offset superior que coincida con la cabecera). O elimina el resaltado activo.
El scroll de la barra lateral roba eventos de rueda de la página
Síntoma: El usuario desplaza sobre la barra lateral y queda “atrapado” desplazando la navegación.
Causa raíz: Las barras laterales son desplazables e interceptan eventos wheel/touch.
Solución: Acéptalo como comportamiento normal, pero mantén las barras laterales cortas cuando sea posible; considera secciones colapsables. Evita hacks agresivos de encadenamiento de scroll salvo que sepas lo que haces.
La impresión es ilegible o falta contenido
Síntoma: La página impresa incluye ruido de nav/TOC o recorta contenido.
Causa raíz: Paneles sticky y fondos no manejados para impresión.
Solución: Añade una regla @media print para ocultar nav/aside y quitar fondos y sombras pesadas.
Listas de verificación / plan paso a paso
Paso a paso: construir el layout (el orden sensato)
- Empieza con HTML semántico:
header,nav,main,aside. Sin wrappers salvo que lo justifiquen. - Haz el grid: columnas izquierda y derecha fijas; centro con
minmax(0, 1fr). - Define restricciones de legibilidad: limita la longitud de línea con
max-widthen el flujo de contenido; haz que los bloques de código se desplacen internamente. - Añade comportamiento sticky: paneles izquierdo y derecho con
topbasado en la altura de la cabecera; usamax-height+overflow: autopara listas largas. - Corrige offsets de anclas:
scroll-margin-topen encabezados. - Colapso responsivo: una sola columna bajo un breakpoint; considera ocultar el TOC derecho.
- Pase de accesibilidad: enlace de salto, estilo de foco, etiquetas de landmark, navegación por teclado.
- Pase de rendimiento: elimina manejadores de scroll costosos; si debes resaltar encabezados, usa IntersectionObserver.
- Endurecimiento para producción: busca wrappers/scripts inyectados; bloquea overflow y transforms alrededor del layout.
Lista operativa: qué revisar en PRs
- No añadir nuevo
overflow: hiddenen wrappers de alto nivel sin justificación y capturas en breakpoints. - No crear nuevas cajas de altura completa
height: 100vhpara páginas de docs (salvo que aceptes las consecuencias y las documentes). - La plantilla de grid mantiene
minmax(0, 1fr)(o equivalentemin-width: 0en el contenido). - Los encabezados tienen IDs estables; no duplicados.
- Existen estilos de impresión que ocultan el desorden de navegación.
- Cualquier JS del TOC es opcional y no rompe sin JS.
Tres minihistorias corporativas desde el terreno
Mini-historia 1: Un incidente causado por una suposición equivocada
Un equipo con el que trabajé desplegó un rediseño de docs dentro de un “app shell unificado”. El shell tenía una cabecera fija, una barra lateral izquierda y una región de contenido interna con height: 100vh y overflow: auto. La suposición era simple: “esto es lo que hacen las apps modernas, y el sticky funcionará dentro”.
Funcionó. En Chrome. En escritorios. En la vía feliz donde la altura de la cabecera nunca cambió.
Luego se inyectó un banner corporativo en la parte superior por un aviso de cumplimiento. La altura de la cabecera aumentó. Los enlaces ancla empezaron a aterrizar debajo de la cabecera. El TOC derecho (que usaba un listener de scroll) calculó offsets incorrectos porque asumía que la raíz de scroll era la ventana, no el contenedor interno. Los usuarios hacían clic en encabezados y pensaban que el contenido faltaba. Llegaron tickets de soporte con capturas de “secciones en blanco” que en realidad estaban ocultas por encima del pliegue.
El incidente no fue un crash. Fue peor: una pérdida lenta de confianza. Los ingenieros dejaron de fiarse de la docs. Preguntaban a colegas en su lugar, que es una forma cara de recuperar información.
La solución fue aburrida: eliminar el contenedor de scroll interno, dejar que el documento se desplace, establecer scroll-margin-top y hacer que el banner forme parte del flujo normal para que el layout se adaptara. La suposición de que “los patrones de app shell son universales” fue la causa raíz. Las docs son contenido primero, no chrome primero.
Mini-historia 2: Una optimización que salió mal
Otra organización quiso “desplazamiento mantecoso” en páginas largas. Alguien introdujo un patrón: aplicar transform: translateZ(0) al wrapper principal para forzar aceleración por compositor, más will-change: transform en algunos paneles. El cambio venía con un video demo bonito.
En producción, el TOC derecho empezó a comportarse de forma inconsistente. En algunas máquinas, el sticky dejó de funcionar cuando el wrapper se convirtió en un bloque contenedor transformado. En otras, funcionaba pero el renderizado de texto cambió ligeramente (diferencias subpixel) y los encabezados se desplazaron lo suficiente como para que los umbrales de IntersectionObserver empezaran a parpadear. El resaltado activo bailaba entre dos secciones mientras el usuario hacía scroll lentamente. Nada dice “ingeniería de calidad” como un widget de navegación con crisis de identidad.
El rendimiento también empeoró en dispositivos de gama baja. El navegador mantuvo capas extra vivas por el will-change, aumentando la presión de memoria y a veces provocando pausas de garbage collection. Los usuarios describieron la página como “entre cortada”, que es la forma UI de decir “tu optimización es el problema”.
La reversión fue inmediata. La solución a largo plazo fue disciplinada: mantener transforms fuera de wrappers de layout, medir el rendimiento real de scroll con un trace y aplicar will-change solo a elementos que realmente animan. La mayoría de las páginas de docs no necesitan animación; necesitan previsibilidad.
Mini-historia 3: Una práctica aburrida pero correcta que salvó el día
Un equipo tenía la costumbre, que parecía tediosa: cada cambio de layout venía con una pequeña página de pruebas “invariantes de layout” en el repo. No era nada fancy. Solo un HTML que incluía contenido patológico: líneas de código muy largas, encabezados profundamente anidados, una lista de nav sobredimensionada, un banner falso en la parte superior y un pie de página.
Antes de enviar, abrían esa página en unos pocos navegadores, redimensionaban el viewport y probaban los anclajes. Tomaba diez minutos. La gente se quejaba, suavemente, como hacen los ingenieros.
Entonces llegó una actualización de un widget de feedback de terceros que envolvió toda la página en un contenedor con overflow: hidden para gestionar sus propias animaciones. El sticky habría roto. Pero porque el equipo tenía la página de invariantes y la probó en staging con integraciones similares a producción, la regresión se detectó antes de la liberación.
La solución no fue heroica: acotar el contenedor del widget para que no envolviera el layout de docs y eliminar la regla de overflow del wrapper de alto nivel. La práctica aburrida—mantener una página de prueba patológica y usarla—salvó días de soporte y evitó enviar una navegación rota a todos los ingenieros de la compañía.
Un chiste corto #2: Lo único más sticky que position: sticky es un informe de bug titulado “navegación de docs rota” presentado cinco minutos antes del freeze de lanzamiento.
Hechos y contexto histórico
- CSS Grid se volvió ampliamente usable en navegadores modernos alrededor de 2017, reemplazando una década de hacks con floats y sistemas frágiles de columnas para muchos layouts.
- Antes de Grid, los layouts “holy grail” de tres columnas se construían típicamente con floats o display tipo tabla, requiriendo trucos de orden fuente.
position: stickynació como una capacidad muy solicitada porquefixedes demasiado bruto para UI en flujo como barras laterales.- El comportamiento sticky depende de contenedores de desplazamiento, y los contenedores de scroll se hicieron más comunes con shells de SPA y librerías de componentes que por defecto usan scroll interno.
- El patrón
minmax(0, 1fr)existe por reglas de dimensionado intrínseco; sin él, contenido como líneas largas de código puede forzar tracks de grid más anchos que el viewport. - Las unidades de carácter (
ch) son una herramienta tipográfica práctica para docs porque escalan con la fuente y representan mejor la longitud de línea que los píxeles. - IntersectionObserver se introdujo para reducir el abuso de handlers de scroll, dando a la plataforma una forma más eficiente de observar cambios de visibilidad.
- Los sitios de docs históricamente confiaron en HTML renderizado en servidor porque el enlace, la impresión y la accesibilidad son no negociables; el routing cliente pesado llegó después y a menudo revirtió estas bases.
Preguntas frecuentes
1) ¿Debería usar CSS Grid o Flexbox para este diseño?
Usa Grid. Esto es literalmente para lo que sirve Grid: dos tracks fijos y uno flexible con gaps limpios. Flexbox puede hacerlo, pero es más frágil cuando añades paneles con desplazamiento independiente y colapso responsive.
2) ¿Por qué insistes en minmax(0, 1fr)?
Porque el tamaño mínimo por defecto de los ítems del grid puede ser su anchura intrínseca. Líneas largas de código forzarán overflow. minmax(0, 1fr) permite explícitamente que el track se encoja.
3) ¿Está bien convertir toda la página en una caja de altura fija con scroll interno?
Puedes, pero compras problemas: enlaces ancla, restauración de scroll, offsets sticky y peculiaridades de navegadores móviles. Para docs, recomiendo dejar que el documento se desplace salvo que tengas un requisito muy específico.
4) Mi elemento sticky funciona hasta que añado overflow: hidden en algún lugar. ¿Por qué?
Porque creaste (o cambiaste) el elemento que define el contexto contenedor del sticky. El posicionamiento sticky es relativo al ancestro con desplazamiento más cercano. Las reglas de overflow a menudo crean ese ancestro o alteran el recorte.
5) ¿Debería generar el TOC derecho con JavaScript?
Si tienes un paso de compilación, genéralo en build. Si no, mantén un TOC estático simple o usa mejora progresiva. No hagas obligatorio JS para la navegación básica en un sitio de docs salvo que disfrutes explicar esa decisión luego.
6) ¿Cómo evito que el TOC sea demasiado alto?
Dale max-height: calc(100dvh - topOffset - bottomGap) y overflow: auto. Déjalo desplazarse internamente. Si sigue siendo masivo, tienes demasiados encabezados; es un problema de diseño de contenido, no de CSS.
7) ¿Cómo evito que el TOC se superponga al contenido en pantallas pequeñas?
Colapsa a una columna en un breakpoint y oculta el TOC derecho o mueve un TOC dentro del contenido al inicio del artículo. Mantener tres columnas en móvil es la forma de obtener scroll horizontal accidental y toques enojados.
8) ¿position: sticky perjudica el rendimiento?
Sticky en sí suele estar bien. Los asesinos de rendimiento son handlers de scroll, thrash de layout y efectos de pintura pesados (sombras, filtros) en paneles sticky grandes. Mide con un trace de performance; no supongas.
9) ¿Qué pasa con safe areas y muescas en móviles?
Si tienes una cabecera sticky, considera usar variables de entorno como los insets de safe-area en tu padding. Pero mantenlo simple: las páginas de docs no deberían tener chrome fijo intrincado en móvil.
10) ¿Puedo soportar impresión limpia con este diseño?
Sí. Oculta nav y TOC en @media print, elimina fondos y sombras, mantén los enlaces subrayados. La impresión aún se usa en auditorías, revisiones y runbooks de incidentes—molestamente a menudo.
Próximos pasos que realmente ayudan
Si quieres que este diseño sobreviva en producción, haz lo aburrido:
- Fija el modelo de desplazamiento: desplazamiento del documento, no un shell interno.
- Haz Grid resiliente: barras laterales fijas,
minmax(0, 1fr)en el centro y evita sorpresas de dimensionado intrínseco. - Haz el sticky predecible: no haya wrappers con overflow/transform alrededor de los elementos sticky.
- Arregla anclas correctamente:
scroll-margin-topen encabezados, no divs hack. - Mantén la mejora del TOC opcional: si el resaltado activo causa jank, elimínalo. Nadie fue notificado porque un TOC no brilló.
Luego ejecuta las mismas comprobaciones que harías para un servicio: reproduce localmente, verifica en un entorno parecido a producción con scripts inyectados y mantén una página de pruebas patológica. Las docs son infraestructura. Trátalas como tal.