Publicas una página de documentación. Alguien pega una tabla Markdown con ocho columnas, un bloque de código con una línea de 300 caracteres y una lista anidada que parece un censo romano. De repente el diseño se mueve, el viewport móvil tiene barras de desplazamiento horizontales y el equipo de diseño aparece con un informe de errores como si fuera una prueba.
Markdown es “simple” hasta que lo ejecutas en producción a escala: múltiples renderizadores, contenido desconocido, HTML impredecible y CSS que se filtra felizmente por todo el sitio. Establezcamos valores sensatos—encabezados, listas, tablas, código, citas—para que tu contenido siga siendo legible y tu interfaz permanezca intacta.
Los principios de producción: alcance, predictibilidad y casos límite feos
Estilar Markdown no es un ejercicio de “elige una fuente bonita”. Es aislar contenido con tipografía. Estás tomando una entrada semi-estructurada y renderizándola dentro de una UI de producto que ya tiene sus propias suposiciones sobre espaciado, colores y diseño. En la práctica, estás defendiendo el resto de la página del contenido y defendiendo el contenido del resto del CSS.
Tres principios te mantienen fuera de problemas:
- Encapsula todo. Mete Markdown dentro de un contenedor como
.mdy solo aplica estilos ahí. Si publicas reglas globales parah1,preotable, no estás “estilando Markdown”. Estás apostando con todo el sitio. - Prefiere ritmo predecible sobre visuales ingeniosos. Los usuarios leen docs bajo estrés: respuesta a incidentes, ventanas de migración, onboarding. Tu CSS debe ser aburrido en el mejor sentido: espaciado consistente, longitud de línea legible, formato de código sensato.
- Diseña para la entrada en el peor escenario. URLs largas, hashes sin guiones, listas profundamente anidadas, tablas anchas y bloques de código que se niegan a envolver. Si solo estilizas el “camino feliz”, tu primer usuario avanzado hará el QA por ti.
Una más: haz que el navegador haga menos trabajo. CSS sofisticado que desencadena reflow, saltos de diseño o tormentas de repintado es cómo “una página de docs” se convierte en “un incidente de rendimiento”. Sí, en serio.
Idea parafraseada de Werner Vogels (ingeniería de fiabilidad): construyes sistemas asumiendo fallos y luego haces que el fallo sea sobrevivible. Eso también aplica al contenido Markdown.
Broma #1: CSS es el único lenguaje donde puedes equivocarte de diecisiete maneras diferentes y aun así publicar una página que “se ve bien” en tu portátil.
Hechos y contexto histórico que explican el desorden actual
Markdown se siente atemporal, pero el ecosistema a su alrededor es un parcheado. Unos pocos hechos ayudan a entender por qué no puedes tratar “estilos Markdown” como un objetivo único:
- Markdown nació como un formato de conveniencia, no como un estándar de publicación. El Markdown original de John Gruber (2004) fue diseñado para escribir HTML más rápido, no para una salida semántica consistente entre implementaciones.
- CommonMark existe porque “Markdown” era demasiado ambiguo. Diferentes parsers discreparon en casos límite (como guiones bajos en palabras, listas anidadas), lo que significa que tu HTML puede variar según el renderer.
- GitHub Flavored Markdown popularizó tablas y listas de tareas. Esas características no eran parte del spec original, pero se volvieron “esperadas”, así que tu CSS debe manejarlas.
- La tipografía web temprana asumía líneas cortas y baja densidad. Muchos valores predeterminados vienen de una era de pantallas 800×600. Hoy, tanto monitores enormes como teléfonos pequeñísimos impactan tu contenido.
- Las bibliotecas de resaltado de código moldearon expectativas de CSS. Herramientas como highlight.js y Prism introdujeron marcado con muchas clases (
spana montones) dentro depre, lo que afecta la altura de línea y la selección. - Los resets de CSS normalizaron diferencias entre navegadores—y luego crearon nuevos conflictos. Un reset puede eliminar
marginen listas y encabezados, dejando el contenido Markdown como un bloque de texto a menos que reintroduzcas ritmo. - Las tablas nunca fueron móviles por defecto. Las tablas HTML preceden al diseño responsive por mucho tiempo; los wrappers con desplazamiento horizontal se volvieron la salida común.
- El modo oscuro es un requisito moderno, no un “agradable de tener”. Una página de docs con bloques de código blancos en modo oscuro se lee como una linterna. Necesitas colores deliberados.
Un conjunto CSS básico que realmente puedes desplegar
Si te llevas una cosa de este artículo: usa una clase contenedora, define un ritmo tipográfico y maneja el desbordamiento. El CSS en el <style> de este documento es exactamente eso: acotado, aburrido y resistente a fallos comunes de contenido.
Esto es por qué la base está estructurada así:
- Variables CSS te dan un panel de ajuste: cambia la pila de fuentes, colores, espaciado y longitud máxima de línea sin buscar selectores.
- Un ancho máximo en caracteres (
78ch) mantiene la longitud de línea legible en todos los monitores. También evita que tablas y bloques de código se conviertan en desastres de diseño a todo ancho. - Scroll margin en encabezados evita que enlaces anclados queden ocultos bajo headers sticky. Te lo agradecerás la primera vez que un runbook en producción use anclas.
- Clase wrapper para tablas (
.table-wrap) te permite contener el desbordamiento horizontal sin hacer que toda la página se desplace lateralmente. - Estilos de impresión evitan que bloques de código oscuros desperdicien tinta y se vuelvan ilegibles.
Lo que no deberías hacer: copiar una hoja de estilos de “tema de blog” con layouts complejos, callouts flotantes y pseudo-elementos decorativos. El contenido Markdown es un adaptador universal; tu CSS debe ser un protector contra sobretensión.
Encabezados: jerarquía sin dramatismo
Los encabezados en Markdown son engañosamente costosos. Afectan la navegación, el enlace por ancla, la legibilidad y la percepción de confianza del documento. Si los encabezados se ven inconsistentes, el contenido parece inconsistente—aunque el contenido sea correcto.
Reglas de espaciado que evitan “docs acordeón”
Modo de fallo: encabezados con márgenes superiores enormes crean un efecto “acordeón” donde el contenido se siente desarticulado y los usuarios pierden contexto. Lo contrario es peor: encabezados sin margen hacen que la página parezca un archivo de registro.
Usa una cadencia consistente:
h2recibe un margen superior mayor para separar secciones claramente.h3recibe un margen superior menor para que las subsecciones no parezcan capítulos separados.- Mantén la altura de línea de los encabezados más ajustada que la del texto para evitar encabezados en varias líneas incómodos.
Anclas y headers sticky
Si tienes un header sticky, el comportamiento de ancla por defecto del navegador desplazará el encabezado debajo de éste. El usuario hace clic en un enlace, la página salta y el título desaparece. Eso no solo es molesto: parece roto.
Arréglalo con scroll-margin-top en los encabezados. Es simple, efectivo y no requiere JavaScript. Usa un valor que coincida con la altura de tu header más un poco de margen.
No estilices encabezados globalmente
Si tu renderer Markdown vive dentro de una app más amplia, los estilos globales de encabezados son cómo accidentalmente restyleas títulos de modales, tarjetas y navegación. Encápsulalo. Siempre.
Listas: espaciado, anidamiento y por qué los viñetas engañan
Las listas son donde el contenido Markdown se vuelve raro. Listas anidadas, mezclas de ordenadas/no ordenadas, listas de tareas y elementos de lista que contienen párrafos pueden producir variaciones de marcado según el renderer.
Espaciado que sobrevive contenido anidado
Los márgenes por defecto de listas varían por navegador. Los resets a menudo eliminan el padding de listas. El resultado: viñetas pegadas al borde, o listas anidadas que parecen una escalera en medio de la página.
Reglas prácticas:
- Usa
padding-leftenul/olen lugar de depender de los marcadores por defecto. - Da a
liun pequeño margen vertical. No un1remcompleto, o tu lista se convierte en un folleto. - Controla explícitamente los márgenes de listas anidadas para que no se inflen.
Listas de tareas y checkboxes
Algunos renderizadores emiten listas de tareas como input type="checkbox" dentro de li. Si estilos globales afectan elementos de formulario, podrías redimensionarlos o restilarlos sin querer. Mantén el estilo de elementos de formulario en Markdown conservador o muy acotado.
Broma #2: las listas anidadas son como organigramas—técnicamente correctas, emocionalmente agotadoras.
Tablas: desbordamiento, alineación y la “sorpresa de ocho columnas”
Las tablas son la principal causa de quejas tipo “¿por qué esta página se desplaza horizontalmente en iPhone?”. El navegador ampliará el viewport sin problema para acomodar una tabla, y tu layout hará un pequeño baile interpretativo.
Envuelve las tablas, no luches contra ellas
Tienes dos estrategias realistas:
- Permitir desplazamiento horizontal. Mete la tabla dentro de un wrapper con
overflow-x: auto. Es la opción menos mala para datos anchos. - Transformar tablas en tarjetas en pantallas pequeñas. Puede funcionar, pero es complejo y frágil con tablas arbitrarias de Markdown. Hazlo solo si controlas el esquema de la tabla.
Para la mayoría de contenido Markdown en libertad, usa el wrapper. Es honesto: datos anchos son datos anchos.
Estabilidad: evitar saltos de diseño cuando las tablas cargan
Las tablas pueden causar layout shift cuando las fuentes cargan tarde o cuando la página se renderiza inicialmente sin los estilos del wrapper. Mantén tu CSS crítico inline o cargado temprano y mantén los estilos de tabla simples.
Alineación numérica
Los docs a menudo incluyen números de versión, tamaños y valores de latencia. Considera font-variant-numeric: tabular-nums en tablas para reducir el jitter. No lo soluciona todo, pero mejora la escaneabilidad si tu fuente lo soporta.
Bloques e inline de código: legibles, copiables y no un rescate UX
El código es el objetivo de la mayoría del Markdown técnico. Si tu estilo de código es incorrecto, el doc está equivocado. Punto.
Inline code: legible sin romper el flujo
El código inline debe parecer código, pero no gritar. Los modos de fallo comunes:
- El inline code tiene demasiado padding y parece un botón.
- El inline code no se envuelve y provoca overflow en móvil por tokens largos.
- El inline code usa una fuente minúscula que se vuelve ilegible al 125% de zoom.
Usa un fondo sutil, un borde fino, padding moderado y white-space: break-spaces para que tokens largos puedan envolver cuando sea necesario.
Bloques de código: el desbordamiento es una característica
No envuelvas bloques de código por defecto. El código envuelto rompe copiar/pegar, oculta indentación y crea comandos ambiguos. La jugada correcta es desplazamiento horizontal dentro del bloque (overflow: auto en pre).
Sí, el desplazamiento horizontal no es “bonito”. También es como funcionan las terminales. Tus usuarios lo pueden manejar.
Altura de línea y tamaño de tabulación
Los bloques de código necesitan una altura de línea un poco más compacta que la prosa, pero no apretada. También define tab-size a algo sensato (2 o 4). La representación por defecto de tabs varía y puede distorsionar la indentación en snippets.
El resaltado de sintaxis no salvará el contraste pobre
Las librerías de resaltado añaden colores, no legibilidad. Comienza con un par de fondo/primer plano de alto contraste y luego aplica colores de resaltado encima. En modo oscuro, evita fondos negro puro: amplifican halos y dificultan ver la selección.
Citas: llamadas sin convertirse en póster motivacional
Las citas en Markdown a menudo se abusan como callouts de “nota/advertencia”. Está bien, pero el estilo debe mantenerlas legibles y claramente distintas del texto principal sin devorar la página.
Usa:
- Un borde izquierdo (afordancia fuerte, poco ruido).
- Un tinte de fondo sutil (opcional, pero útil).
- Padding cómodo para que citas con varios párrafos no queden apretadas.
Evita comillas de cita sobredimensionadas, itálicas gigantes o cambios dramáticos de fuente. Esto no es una invitación de boda.
Accesibilidad y legibilidad: contraste, foco, selección y movimiento
Los docs son para todos, incluyendo a la persona que lee tu runbook de incidentes a las 3 a. m. con un ojo medio abierto y el brillo del portátil en “arrepentimiento”. La accesibilidad no es caridad; es corrección operacional.
Contraste y elección de colores
Los enlaces deben ser claramente enlaces. Usa subrayado por defecto o al menos en hover con contraste fuerte. Tampoco dependas solo del color para diferenciar significado dentro de los bloques de código: los colores de resaltado deben complementar, no reemplazar, el contraste base.
Color de selección
La gente copia de docs constantemente. Un ::selection personalizado con contraste suficiente facilita copiar sin errores, especialmente en código. Mantenlo sutil pero visible.
Contornos de foco
Si tu app elimina contornos de foco globalmente, los enlaces en Markdown se vuelven hostiles para teclado. Arréglalo globalmente en la app o restáuralo dentro de .md. “Soportamos navegación por teclado” o no lo soportamos.
Zoom y tamaños de fuente
No fijes el tamaño de fuente del contenedor Markdown en px y luego todo lo demás también en px. Usa rem y deja que los usuarios hagan zoom. El objetivo de diseño es “legible”, no “idéntico”.
Rendimiento y estabilidad: prevenir saltos de diseño y colisiones CSS
La mayoría de problemas de CSS aparecen como “se ve raro”. Los costosos aparecen como “se siente lento” y “salta”. Ahí ayudan los instintos SRE: busca causas sistémicas, no solo fealdad local.
Prevenir layout shift (CLS) en páginas Markdown
Fuentes comunes de layout shift en contenido Markdown:
- Fuentes que cargan tarde. El texto refluye cuando la fuente se intercambia.
- Imágenes sin dimensiones. La página colapsa y luego se expande cuando la imagen carga.
- Tablas y bloques de código que se expanden tras el render. Si tu CSS llega tarde, el render inicial puede estar sin estilo.
Mitigaciones prácticas:
- Prefiere pilas de fuentes del sistema para docs, o precarga la fuente que uses.
- Asegura que tu pipeline Markdown añada
widthyheightpara imágenes cuando sea posible, o usa patrones de aspect-ratio si controlas la salida. - Inlinea CSS crítico mínimo para
.md(espaciado, bloques de código, tablas) para que el primer render sea estable.
Colisiones de CSS: por qué el scoping es innegociable
La salida Markdown es HTML: encabezados, listas, tablas, pre, code, blockquote. Tu app casi seguro ya estiliza estos globalmente. Si no encapasulas estilos Markdown, o bien:
- Romperás componentes de la app con estilos “de docs”, o
- Romperás los docs al heredar estilos de componentes que nunca pretendiste usar.
Encápsula con una clase contenedora. Si quieres seguridad extra, usa capas de cascada y mantén Markdown en una capa de prioridad menor que tus componentes del sistema de diseño—o al revés, según la ley del producto. Pero elige una y hazla cumplir.
Guía rápida de diagnóstico
Cuando alguien reporta “la página Markdown está rota” necesitas encontrar el cuello de botella rápido: renderizado, colisiones CSS, overflow o rendimiento. Aquí el orden que ahorra tiempo.
Primero: aislar alcance y colisiones
- Inspecciona el elemento contenedor Markdown. Confirma que tenga una clase de scope dedicada (como
.md). - En DevTools, revisa estilos computados para
h2,ul,pre,table. Si vienen de un reset global o una librería de componentes, tienes una colisión. - Desactiva temporalmente tipografía/reset global para ver si Markdown vuelve a la normalidad. Si sí: arregla el scoping y el orden de la cascade.
Segundo: identificar el culpable del overflow
- Busca desplazamiento horizontal en la página. Encuentra qué elemento excede el viewport.
- Ofensores comunes:
table,pre, cadenas largas sin romper en párrafos, imágenes con anchos fijos. - Añade contención: envuelve tablas, pon
overflow: autoen bloques de código y aplica reglas de ruptura solo a la prosa—no a los bloques de código.
Tercero: revisar rendimiento y layout shift
- Mide CLS y comportamiento de intercambio de fuentes. Si la página salta, el CSS o las fuentes llegan tarde.
- Verifica que las imágenes tengan dimensiones. Si no, arregla en la capa de procesamiento Markdown.
- Confirma que el resaltado de sintaxis no haga trabajo DOM pesado en cada render.
Tareas prácticas: comandos, salidas y qué decides después
Estas son las tareas que realmente ejecuto cuando un “problema de estilos en docs” golpea producción. Se enfocan en el pipeline: artefactos de build, payload CSS, caching y regresiones. Cada una incluye un comando, salida de ejemplo y la decisión que tomas a partir de ello.
Task 1: Verify the Markdown HTML output is what you think it is
cr0x@server:~$ node -e "const fs=require('fs');const md=fs.readFileSync('sample.md','utf8');const {marked}=require('marked');console.log(marked.parse(md).slice(0,800));"
<h1>Runbook: Database failover</h1>
<p>If replication is behind, stop and verify.</p>
<h2>Prereqs</h2>
<ul>
<li>Access to <code>prod-bastion</code></li>
<li>Current primary is <code>db-01</code></li>
</ul>
<pre><code class="language-bash">...</code></pre>
Qué significa: Confirmas si tu renderer emite <pre><code> con clases, cómo se anidan las listas y si hay tablas.
Decisión: Si la salida difiere entre entornos (servidor vs cliente), para. Alinea el renderer o normaliza la salida antes de tocar CSS.
Task 2: Check which CSS rules win (collision audit)
cr0x@server:~$ rg -n "pre\\s*\\{|code\\s*\\{|table\\s*\\{|blockquote\\s*\\{|h2\\s*\\{" dist/assets/*.css | head
dist/assets/app.4f19c8.css:231:pre{white-space:pre-wrap;word-break:break-word}
dist/assets/app.4f19c8.css:877:table{width:100%;font-size:12px}
dist/assets/docs.8a02b1.css:44:.md pre{overflow:auto;background:#0b1220}
dist/assets/docs.8a02b1.css:71:.md table{border-collapse:collapse}
Qué significa: Ves reglas globales como pre{white-space:pre-wrap} que pueden romper bloques de código.
Decisión: Mueve estilos Markdown a un archivo scopeado cargado después del reset, o cambia el reset para no afectar pre globalmente.
Task 3: Measure CSS payload size (docs styles should be lean)
cr0x@server:~$ ls -lh dist/assets/*css | sed -n '1,10p'
-rw-r--r-- 1 cr0x cr0x 412K Dec 29 11:20 dist/assets/app.4f19c8.css
-rw-r--r-- 1 cr0x cr0x 18K Dec 29 11:20 dist/assets/docs.8a02b1.css
Qué significa: El CSS de docs está separado y es pequeño. Bien. Si es gigantesco, probablemente estás importando todo el sistema de diseño para una página de runbook.
Decisión: Si el CSS de docs está inflado, separa bundles o inlinea solo estilos críticos de docs.
Task 4: Confirm gzip/brotli is active for CSS
cr0x@server:~$ curl -sI -H 'Accept-Encoding: br' http://localhost:8080/assets/docs.8a02b1.css | sed -n '1,12p'
HTTP/1.1 200 OK
Content-Type: text/css; charset=utf-8
Content-Encoding: br
Cache-Control: public, max-age=31536000, immutable
Vary: Accept-Encoding
ETag: "8a02b1"
Qué significa: La compresión está activa, el caching es inmutable y el servidor varía por encoding. Esa es la base para entrega rápida de docs.
Decisión: Si falta compresión, arregla la configuración del servidor antes de micro-optimizar selectores.
Task 5: Verify caching headers for rendered Markdown pages
cr0x@server:~$ curl -sI http://localhost:8080/docs/runbook/db-failover | sed -n '1,14p'
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-store
Vary: Accept-Encoding
Qué significa: La página no está cacheada. Puede ser correcto para runbooks autenticados, o puede ser accidental.
Decisión: Si el contenido es estático y público, quieres caching. Si es privado o por usuario, mantén no-store pero asegura que los assets se cacheen agresivamente.
Task 6: Find which element causes horizontal scrolling
cr0x@server:~$ node -e "console.log('Use DevTools: run in console: [...document.querySelectorAll(\"body *\")].filter(e=>e.scrollWidth>e.clientWidth).slice(0,5).map(e=>[e.tagName,e.className,e.scrollWidth-e.clientWidth]) )')"
Use DevTools: run in console: [...document.querySelectorAll("body *")].filter(e=>e.scrollWidth>e.clientWidth).slice(0,5).map(e=>[e.tagName,e.className,e.scrollWidth-e.clientWidth])
Qué significa: Estás usando un método confiable para encontrar ofensores de overflow en lugar de adivinar.
Decisión: Si los ofensores son TABLE o PRE, añade wrappers/overflow. Si son párrafos, agrega reglas de ruptura seguras para prosa.
Task 7: Detect unbroken strings in Markdown that will overflow
cr0x@server:~$ perl -ne 'while(/(\S{80,})/g){print "$ARGV:$.:$1\n"}' docs/**/*.md | head
docs/runbook/net-debug.md:42:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
docs/howto/api.md:118:sha256:8c3d0f0a1c7b9d2e4f6a9b1c3d5e7f9a0b2c4d6e8f0a2b4c6d8e0f2a4c6d8e0f2
Qué significa: Encontraste tokens que romperán el diseño a menos que permitas ruptura en la prosa.
Decisión: Añade overflow-wrap: anywhere en .md p si es necesario, pero no lo apliques a pre/code.
Task 8: Confirm code blocks aren’t being wrapped by a reset
cr0x@server:~$ rg -n "pre\\s*\\{[^}]*white-space:\\s*pre-wrap" dist/assets/app.*.css
dist/assets/app.4f19c8.css:231:pre{white-space:pre-wrap;word-break:break-word}
Qué significa: El reset fuerza wrapping y rompe código sensible a indentación.
Decisión: Sobrescribe dentro de .md con .md pre{white-space:pre;} y elimina la regla global si es posible.
Task 9: Validate that Markdown headings have stable anchor IDs
cr0x@server:~$ node -e "const s=require('fs').readFileSync('sample.html','utf8');const ids=[...s.matchAll(/id=\"([^\"]+)\"/g)].map(m=>m[1]);console.log(ids.filter(x=>x.includes(' ')).slice(0,10));"
[]
Qué significa: No hay IDs con espacios. Bien. IDs de ancla con caracteres inseguros suelen romper TOCs y enlaces profundos.
Decisión: Si los IDs son inestables, arregla la generación de slugs en el renderer en lugar de retocar CSS.
Task 10: Ensure tables are wrapped (if your renderer supports post-processing)
cr0x@server:~$ node -e "const {JSDOM}=require('jsdom');const fs=require('fs');const html=fs.readFileSync('sample.html','utf8');const dom=new JSDOM(html);const d=dom.window.document;d.querySelectorAll('.md table').forEach(t=>{const w=d.createElement('div');w.className='table-wrap';t.parentNode.insertBefore(w,t);w.appendChild(t);});console.log(d.querySelectorAll('.table-wrap table').length);"
3
Qué significa: Puedes forzar un wrapper incluso si los autores olvidan añadirlo.
Decisión: Si las tablas desbordan con frecuencia, haz el wrapping automático en el pipeline. No dependas de la disciplina del autor.
Task 11: Check if your syntax highlighter bloats the DOM
cr0x@server:~$ node -e "const fs=require('fs');const html=fs.readFileSync('sample.html','utf8');const spans=(html.match(/
Qué significa: El resaltado inyectó muchas spans. Eso puede impactar el rendimiento, especialmente en dispositivos de gama baja.
Decisión: Si el conteo de spans es enorme, considera resaltado en servidor para docs estáticos, limita idiomas o usa temas más ligeros.
Task 12: Inspect Lighthouse-like signals for layout shift in CI (quick and dirty)
cr0x@server:~$ node -e "console.log('Run a headless audit in CI (example): npx playwright test --project=chromium; capture CLS via PerformanceObserver and fail if above threshold')"
Run a headless audit in CI (example): npx playwright test --project=chromium; capture CLS via PerformanceObserver and fail if above threshold
Qué significa: Estás convirtiendo “alguien notó que salta” en una prueba de regresión.
Decisión: Si CLS empeora, trátalo como deuda de rendimiento: arregla fuentes, dimensiones de imágenes u orden de carga de CSS.
Task 13: Confirm fonts are not blocking rendering
cr0x@server:~$ rg -n "@font-face" dist/assets/*.css | head
dist/assets/app.4f19c8.css:12:@font-face{font-family:Inter;src:url(/assets/Inter.woff2) format("woff2");font-display:swap}
Qué significa: font-display: swap está configurado, reduciendo FOIT (flash de texto invisible).
Decisión: Si falta swap y ves texto invisible, añádelo. O usa una pila de fuentes del sistema para páginas Markdown.
Task 14: Validate CSS scoping is enforced (no global tag selectors in docs CSS)
cr0x@server:~$ awk 'length($0)<500{print}' dist/assets/docs.8a02b1.css | rg -n "^(h1|h2|h3|p|pre|code|table|ul|ol|blockquote)\b" | head
Qué significa: Sin coincidencias: la hoja de estilos de docs no contiene selectores de etiqueta sin scope en el nivel superior.
Decisión: Si ves selectores globales, refactorízalos a .md h2 etc. No negocies con futuras interrupciones.
Errores comunes: síntomas → causa raíz → solución
1) Síntoma: los bloques de código se envuelven y los comandos no se pueden copiar
Causa raíz: Un reset global establece pre { white-space: pre-wrap; word-break: break-word; } o aplica word-break a todo code.
Solución: Dentro del scope Markdown, establece explícitamente .md pre { overflow:auto; } y .md pre code { white-space: pre; }. Elimina reglas globales si puedes.
2) Síntoma: la página móvil tiene desplazamiento horizontal aun sin tablas
Causa raíz: Tokens sin romper en la prosa (hashes, URLs largas, blobs base64) combinados con white-space: nowrap heredado de un estilo de componente.
Solución: Establece .md p { overflow-wrap: anywhere; } o word-break: break-word; solo para la prosa. Mantén los bloques de código con white-space: pre.
3) Síntoma: las listas parecen un párrafo denso
Causa raíz: El CSS reset eliminó márgenes/padding de listas; no se proporcionó ritmo de reemplazo.
Solución: Restaura padding y márgenes de listas dentro de .md. Añade pequeños márgenes verticales a li y controla el espaciado de listas anidadas.
4) Síntoma: las tablas estallan el layout o se encogen a texto ilegible
Causa raíz: Ancho de tabla forzado al 100% más una fuente global muy pequeña, o tablas sin wrapping para overflow.
Solución: Envuelve tablas con overflow-x:auto. Mantén el tamaño de fuente en lo básico. Si es necesario, permite min-width en columnas o confía en el desplazamiento horizontal.
5) Síntoma: los encabezados se solapan con el header sticky al usar anclas
Causa raíz: No hay comportamiento de offset de scroll configurado.
Solución: Añade scroll-margin-top a encabezados dentro del scope Markdown. Usa un valor alineado a la altura del header.
6) Síntoma: el inline code parece botones clicables y distrae
Causa raíz: code sobre-estilizado con padding grande, fondo pesado y alto contraste.
Solución: Reduce padding, usa fondo sutil y borde fino, y mantén el tamaño de fuente cercano al del cuerpo.
7) Síntoma: docs en modo oscuro son ilegibles (demasiado débiles o demasiado brillantes)
Causa raíz: Colores elegidos para modo claro invertidos pobremente; bloques de código permanecen claros o usan negro puro.
Solución: Define variables separadas para modo oscuro. Usa fondos azulados oscuros, no negro puro, y mantén los colores de enlaces legibles.
8) Síntoma: “¿por qué esta página de docs cambió el estilo de nuestra página de checkout?”
Causa raíz: La hoja de estilos Markdown usa selectores de etiqueta globales (h2 { ... }, table { ... }) que se despliegan en todas las páginas.
Solución: Encapsula todo bajo una clase contenedora; carga el CSS de docs solo donde sea necesario; considera capas de cascade.
Tres micro-historias corporativas desde las trincheras Markdown
Incidente causado por una suposición errónea: “Nuestro renderer Markdown siempre emite el mismo HTML”
Una empresa con la que trabajé tenía dos caminos de render: server-side para docs públicas y client-side para runbooks internos. Empezó como una optimización: las páginas internas renderizaban más rápido tras la navegación y el equipo del portal interno podía iterar sin redeployar backend.
Todos asumieron “Markdown es Markdown”, es decir, el HTML sería el mismo. No lo era. El renderer del servidor producía <pre><code class="language-bash">. El renderer cliente producía <pre class="language-bash"><code>. El CSS, naturalmente, apuntaba solo a una de esas formas.
El fallo apareció durante un incidente: un ingeniero on-call pegó un comando desde un runbook, pero el bloque de código se había envuelto en medio de un flag debido a una regla global de pre. El comando pegado falló. El ingeniero lo reintentó con ediciones, luego dejó de confiar en el runbook y empezó a improvisar. El tiempo se fue al garete.
El postmortem mostró que la causa raíz no fue “CSS malo”, sino “dos renderers, un modelo mental”. La solución fue doble: normalizar la salida Markdown (elegir un renderer o post-procesar a una forma HTML canónica) y scopear el CSS de bloques de código para manejar ambas formas. Después añadieron una prueba que renderiza el mismo Markdown por ambos caminos y hace diff del DOM para nodos clave.
Optimización que salió mal: “Reducimos nodos DOM quitando wrappers”
Otro equipo quería acelerar las páginas de docs simplificando el HTML generado. Quitaron elementos wrapper alrededor de tablas y bloques de código porque “divs extra son malos”. Filosóficamente limpio. Operativamente desastroso.
Sin un wrapper de tabla, la única forma de evitar que una tabla ancha rompa el layout era aplicar reglas de overflow directamente a la tabla o a su contenedor padre. Pero el overflow no funciona en tablas como la gente espera, y el contenedor padre también contenía texto normal. De repente toda la página tenía comportamiento de scroll horizontal, porque una tabla era ancha.
Luego “optimizó” aún más aplicando word-break: break-all al contenedor Markdown para prevenir overflow. Eso detuvo el scroll, y también rompió copiar/pegar de hashes, URLs y código inline. On-call empezó a ver tickets del tipo “tus docs corrompen comandos”. Lo cual es un tipo especial de vergüenza porque el contenido estaba correcto; el CSS mentía.
Eventualmente reintrodujeron wrappers para tablas y bloques de código, pero de forma controlada: el renderer añade wrappers solo cuando es necesario (si hay tabla, si hay bloque de código). La página volvió a ser estable y la ganancia de rendimiento de quitar wrappers resultó ser mayormente imaginaria frente al costo de reflow por problemas de layout.
Práctica aburrida pero correcta que salvó el día: “Snapshots de estilos de docs y pruebas de casos raros”
Una organización que respeto trató los estilos de docs como una API. Tenían un pequeño conjunto de fixtures de “Markdown malvado”: listas profundamente anidadas, tokens largos, tablas anchas, inline code mezclado, blockquotes con listas dentro, imágenes y un bloque de código con una línea de comando larga.
Cada cambio en el CSS tipográfico pasaba por snapshots visuales en CI en tres anchos de viewport y dos esquemas de color. No era glamuroso. Nadie fue promovido por “reducir margen de li en 2px”. Pero evitó regresiones que habrían afectado a la audiencia más perjudicada: gente intentando trabajar.
Durante un rediseño, un reset global llegó que eliminó toda la indentación de listas y cambió pre para envolver. Los fixtures lo detectaron antes del release. La solución fue una sobrescritura scopeada dentro de .md y una regla: los resets globales no pueden afectar etiquetas HTML crudas sin la aprobación de un dueño del sistema de diseño.
La práctica aburrida—fixtures, snapshots, CSS scopeado—hizo que el rediseño se lanzara sin que los docs se convirtieran en daño colateral. Así se ve la “fiabilidad” cuando el sistema es mayormente texto.
Listas de verificación / plan paso a paso
Paso a paso para implementar CSS sensato para Markdown
- Introduce una clase contenedora para Markdown. Envuelve todo Markdown renderizado en
<div class="md">...</div>. Sin excepciones. - Define variables para tipografía y espaciado. Establece la pila base de fuentes, color de texto, color de enlaces, bordes y escala de espaciado.
- Restaura ritmo tipográfico. Define márgenes para
p,h2,h3y listas dentro de.md. - Endurece el comportamiento de overflow. Bloques de código:
overflow:auto. Tablas: wrapper con desplazamiento. Prosa: permitir ruptura de tokens largos. - Haz que las anclas funcionen. Añade
scroll-margin-topa encabezados y asegura IDs estables. - Maneja el modo oscuro deliberadamente. Usa variables CSS con overrides en
prefers-color-scheme: dark. - Añade estilos de impresión. Especialmente para bloques de código y enlaces.
- Crea fixtures Markdown. Incluye ejemplos en el peor caso. Mantenlo pequeño pero dañino.
- Automatiza la verificación. Tests de snapshot y un detector simple de overflow. Haz que fallen builds en regresiones.
- Publica CSS scopeado solo donde se necesita. Si docs están en una ruta dedicada, no cargues la hoja Markdown en todas partes.
Checklist previa al despliegue de un cambio en estilos Markdown
- ¿El cambio afecta solo el scope
.md? - ¿Probaste una tabla ancha en un viewport estrecho?
- ¿Probaste un token largo sin romper en un párrafo?
- ¿Una línea larga de código sigue siendo copiables?
- ¿Los enlaces anclados aterrizan correctamente bajo un header sticky?
- ¿El modo oscuro mantiene contraste en enlaces y código?
- ¿La impresión produce bloques de código legibles?
Checklist de rollback
- ¿Puedes desactivar el CSS nuevo de docs vía feature flag o config por ruta?
- ¿Tienes un artefacto de hoja de estilos conocido y bueno que puedas redeplegar rápido?
- ¿Tienes un estilo mínimo “modo seguro” (fuentes del sistema, espaciado básico, reglas de overflow) para volver a él?
Preguntas frecuentes
1) ¿Debo usar un reset CSS para contenido Markdown?
No directamente. Usa un reset a nivel de aplicación si debes, pero luego reconstruye explícitamente espaciado y tipografía dentro de .md. Los docs necesitan márgenes; docs sin márgenes son hostiles.
2) ¿Está bien envolver bloques de código en lugar de permitir scroll?
Sólo si aceptas comandos rotos al copiar/pegar y una indentación ambigua. Para runbooks y comandos, por defecto usa desplazamiento horizontal. Si necesitas wrapping para código tipo prosa (raro), hazlo opt-in.
3) ¿Cómo hago tablas responsivas sin scroll horizontal?
Puedes transformar filas en tarjetas con CSS, pero asume que conoces el esquema. Con tablas arbitrarias de Markdown, el desplazamiento horizontal es el comportamiento más fiable.
4) ¿Por qué mi Markdown se ve diferente entre ambientes?
Diferentes renderizadores (o versiones) producen estructuras HTML distintas. Alinea primero el renderer. El CSS puede parchear diferencias, pero perseguirás casos límite para siempre.
5) ¿Necesito estilos separados para Markdown renderizado en servidor y en cliente?
Necesitas estilos que coincidan con el HTML emitido. La mejor solución es que el HTML emitido sea consistente entre rutas de render y luego escribir una sola hoja scopeada.
6) ¿Cómo evito que los estilos Markdown afecten al resto de la app?
Encapsula con una clase contenedora y evita selectores globales de etiqueta. También considera cargar el CSS de docs sólo en rutas de docs. “Tendremos cuidado” no es una estrategia.
7) Temas de resaltado: ¿elijo uno y ya?
Elige un tema con buen contraste que no dependa de tamaños de fuente minúsculos. Luego verifica visibilidad de selección y copiar/pegar. Un resaltado que luce bonito pero es ilegible en modo oscuro es una regresión.
8) ¿El contenido Markdown debe usar la misma tipografía que la UI del producto?
Usualmente sí para el texto del cuerpo, pero los bloques de código siempre deben usar una pila monoespaciada sólida. Mantén los docs familiares, pero no fuerces restricciones de UI que dañen la legibilidad (como tarjetas estrechas o fuentes diminutas).
9) ¿Puedo usar una librería popular de “Markdown CSS”?
Puedes, pero trátala como código de terceros: audítala, scopeala y prueba tus contenidos peor-caso. Muchas librerías asumen posts de blog, no runbooks.
10) ¿Cuál es el CSS mínimo para un render aceptable de Markdown?
Márgenes scopeados para encabezados/párrafos/listas, estilo de overflow para bloques de código, wrapper para tablas y estilo de enlaces. Todo lo demás es calidad de vida.
Conclusión: próximos pasos que puedes hacer esta semana
El estilo de Markdown se vuelve “difícil” cuando lo tratas como decorativo. Trátalo como una interfaz de producción: entrada semi-confiable, salida variable y usuarios reales dependiendo de ello para trabajo real.
Haz esto a continuación:
- Envuelve el Markdown renderizado en una clase de scope dedicada y deja de estilizar etiquetas crudas globalmente.
- Implementa la base: espaciado consistente en encabezados/listas, overflow en bloques de código y wrappers para tablas.
- Construye un pequeño conjunto de fixtures de Markdown horrible y genera snapshots en múltiples viewports y esquemas de color.
- Añade un detector rápido de overflow y una guardia CLS en CI para que “se ve bien en mi máquina” deje de ser tu plan de QA.
Cuando tus docs sean estables, tu equipo on-call será más rápido, el onboarding más fluido y tu equipo de UI dejará de tratar Markdown como un subsistema rebelde. Eso es una ganancia real en fiabilidad—simplemente hecha de texto.