El método aplicado a cuatro bugs reales
Cuatro bugs clásicos recorridos paso a paso con la secuencia completa: el elemento fijo que no es fijo, el desbordamiento horizontal, la cuadrícula que ignora fr y el sticky que no pega.
Un método que solo se enuncia no se aprende. Estos cuatro bugs son los que más veces vas a ver en tu carrera, y aquí están recorridos con la secuencia completa, incluidas las preguntas que no dan respuesta —que también forman parte del método— y la corrección final. El objetivo no es que memorices las cuatro causas, sino que reconozcas el ritmo del recorrido.
- Recorrer la secuencia completa sobre casos concretos y ver qué descarta cada paso.
- Reconocer las firmas típicas de los cuatro bugs de layout más frecuentes.
- Aplicar la corrección correcta en lugar del parche que oculta el síntoma.
- Distinguir un síntoma de una causa en bugs donde están a varios niveles de distancia.
Bug 1: el elemento fijo que se mueve con el scroll
Síntoma. Un panel lateral con position: fixed se desplaza con la página en lugar de quedarse quieto. Solo ocurre en una vista concreta.
Paso cero. El panel debería estar a 0 del borde superior de la ventana en todo momento; medido con la página desplazada 400 píxeles, getBoundingClientRect().top devuelve -400. Es decir, se comporta como un absolute.
Partición. El computado de position es fixed. La declaración llega. Es un problema de modelo, no de cascada. La rama de cascada queda descartada entera.
Primera pregunta, contexto de formato. El padre es un bloque normal. No explica nada. Sigue.
Segunda, de dónde viene el tamaño. No aplica: el problema es de posición, no de tamaño.
Tercera, el bloque contenedor real. Aquí está. Subiendo por el árbol y mirando los computados de las propiedades que establecen bloque contenedor, tres niveles arriba hay un contenedor con transform: translateZ(0), puesto hace dos años por alguien para “forzar aceleración por hardware”. Cualquier transform distinto de none convierte al elemento en bloque contenedor de sus descendientes fijos, y a partir de ahí fixed se ancla a él y no a la ventana.
Corrección. Quitar el transform, que además no hacía falta: promocionar a capa por si acaso es exactamente el antipatrón descrito en el capítulo de will-change. Si el transform fuera necesario, la corrección es sacar el elemento fijo de ese subárbol, o usar la capa superior con popover, que no se ve afectada por bloques contenedores.
Firma para reconocerlo. Un fixed que se comporta como absolute, en una vista concreta y no en otras. Los sospechosos son siempre los mismos: transform, filter, backdrop-filter, perspective, contain con layout o paint, content-visibility y will-change de cualquiera de ellas.
Bug 2: la barra de scroll horizontal que nadie ha pedido
Síntoma. La página tiene scroll horizontal de unos veinte píxeles. Nada parece sobresalir.
Paso cero. document.documentElement.scrollWidth devuelve 1460 y clientWidth devuelve 1440. Sobran 20 píxeles.
Antes de la partición. No hay ninguna declaración que investigar todavía porque no sabemos qué elemento. Identificar al culpable es el paso previo, y la técnica de contorno universal lo resuelve:
* { outline: 1px solid oklch(0.7 0.2 20); }
Con eso se ve que un carrusel dentro del pie sobresale por la derecha.
Partición. El carrusel tiene width: 100% computado a 1440. Correcto. El problema no es su ancho: es su contenido. Problema de modelo.
Primera pregunta. El carrusel es un contenedor flexible con flex-wrap: nowrap. Relevante.
Segunda, de dónde viene el tamaño. Del contenido. Los hijos tienen flex-shrink: 1, así que deberían encogerse, y no lo hacen. La causa es el tamaño mínimo automático: un elemento flexible tiene min-width: auto, lo que significa que no se encoge por debajo del ancho mínimo de su contenido. Uno de los elementos contiene un título con white-space: nowrap, cuyo mínimo son 380 píxeles y no cede.
Corrección. Depende de la intención. Si el carrusel debe desplazarse horizontalmente, el arreglo es contener el scroll en él con overflow-x: auto y overscroll-behavior-inline: contain, no dejar que empuje al documento. Si los elementos deben encogerse, min-inline-size: 0 en los hijos flexibles libera el mínimo automático.
Lo que no hay que hacer. overflow-x: hidden en body. Es el parche universal para este bug, oculta el síntoma, rompe el desplazamiento programático hasta un elemento y el anclaje de scroll, y deja la causa intacta para que reaparezca en otro sitio.
Bug 3: la columna 1fr que no ocupa lo que debe
Síntoma. Una cuadrícula con grid-template-columns: 1fr 1fr reparte 900 y 540 píxeles en lugar de 720 y 720.
Paso cero. Diferencia medida: 180 píxeles de más en la primera columna.
Partición. El computado de grid-template-columns es 1fr 1fr. La declaración llega. Problema de modelo.
Primera pregunta. El contenedor es grid. Correcto.
Segunda, de dónde viene el tamaño. Del contenido, y aquí está la causa. 1fr no significa “la mitad”: significa “una fracción del espacio libre”, y su tamaño mínimo por defecto es auto, es decir, el mínimo de contenido de la pista. Si el contenido de la primera columna tiene un mínimo de 900 píxeles —una tabla, un bloque de código sin ajuste, una imagen— la pista no baja de ahí y el reparto se hace con lo que sobra.
El overlay de cuadrícula lo confirma de un vistazo: muestra el tamaño resuelto de cada pista y se ve que la primera está en su mínimo.
Corrección. minmax(0, 1fr) en lugar de 1fr. Fuerza el mínimo a cero y hace que el reparto sea el que esperabas; el contenido que no cabe se resolverá con su propio overflow. No es un truco: 1fr es azúcar para minmax(auto, 1fr), y escribir minmax(0, 1fr) es simplemente decir la otra cosa.
Firma. Pistas de cuadrícula o elementos flexibles que no se encogen. Es el mismo mecanismo que el bug 2 con otro nombre: el tamaño mínimo automático de contenido. Reconocer que los dos son el mismo problema vale más que memorizar las dos correcciones.
Bug 4: el sticky que no pega
Síntoma. Un encabezado de sección con position: sticky y top: 0 se desplaza como si fuera estático.
Paso cero. Debería quedarse a 0 del borde superior del contenedor con scroll al alcanzarlo; no lo hace nunca.
Partición. position computa a sticky y top a 0px. Las dos declaraciones llegan. Problema de modelo.
Primera pregunta, contexto de formato. El padre es un bloque normal, y sticky funciona en flujo normal. No es eso.
Cuarta pregunta, quién recorta y quién desplaza. Conviene saltar aquí directamente, porque sticky tiene tres condiciones conocidas y dos se comprueban en el árbol de antepasados:
- Tiene que haber desplazamiento en el eje correcto. El elemento se ancla dentro de su antepasado con scroll más cercano, no en la ventana. Si hay un contenedor intermedio con
overflow: hidden, ese contenedor es su ámbito de anclaje, y dentro de él no hay scroll: nunca se activa. - El padre tiene que ser más alto que el elemento. Un
stickycuyo padre mide exactamente lo mismo que él no tiene recorrido donde pegarse. - Tiene que haber al menos un desplazamiento declarado, porque sin umbral no hay anclaje.
Subiendo por el árbol se encuentra un contenedor con overflow: hidden, puesto para recortar una decoración. Ese es el ámbito de anclaje, y dentro de él no hay scroll.
Corrección. Quitar el overflow: hidden y recortar la decoración de otra forma, con clip-path o con un pseudo-elemento que tenga su propio recorte. Si el overflow es imprescindible, el sticky tiene que salir de ese subárbol.
El otro cincuenta por ciento de estos casos es el sticky dentro de una celda de cuadrícula o de un elemento flexible: el estiramiento por defecto hace que el elemento mida lo mismo que su contenedor y desaparezca el recorrido. Se corrige con align-self: start en el elemento.
En los cuatro, la declaración llegaba correctamente y la causa estaba en un antepasado o en un descendiente, no en el elemento que se veía mal. Esa es la firma general de los bugs de layout, y la razón por la que mirar solo el elemento afectado casi nunca funciona. Las preguntas del bloque contenedor y del origen del tamaño existen precisamente para obligarte a salir del elemento.
Si repasas los cuatro casos y les quitas el detalle, quedan exactamente dos causas. La primera: un antepasado ha cambiado silenciosamente las reglas del juego para todo su subárbol. Un transform lo convierte en bloque contenedor y fixed deja de ser fijo; un overflow lo convierte en ámbito de anclaje y sticky deja de pegarse; un contain hace las dos cosas a la vez; un z-index sobre un antepasado posicionado crea un contexto de apilamiento y tu elemento ya no puede subir por encima de nada de fuera. Son propiedades que la gente pone por un motivo estrictamente local —recortar algo, acelerar algo, ordenar algo— sin saber que están firmando un contrato que afecta a todos sus descendientes, y por eso el síntoma aparece siempre lejos de la causa. La segunda: algo no puede encogerse por debajo de su contenido, porque CSS ha decidido por defecto que proteger el contenido de volverse ilegible importa más que respetar tu ancho. Ese es el mismo mecanismo detrás de min-width: auto en Flexbox, del mínimo automático de las pistas de cuadrícula, de las tablas que ignoran los porcentajes y de las imágenes que desbordan sin max-width. Interiorizar esas dos causas cambia el orden en que miras: ante cualquier bug de posición, sube por el árbol buscando al antepasado que cambió las reglas; ante cualquier bug de tamaño o de desbordamiento, baja buscando el mínimo que no cede. Con esas dos direcciones y las seis preguntas del método cubres, a ojo, nueve de cada diez bugs de layout que te vas a encontrar en tu vida profesional.
Construye los cuatro bugs en un documento vacío, uno por uno, y recorre la secuencia completa en cada uno aunque ya sepas la respuesta. El objetivo no es encontrar la causa —la sabes— sino cronometrar cuánto tardas en responder cada pregunta y detectar cuál de las seis se te atraganta. Esa es la que te va a costar cuando el bug sea real, y casi siempre es la del bloque contenedor, porque exige subir por el árbol comprobando computados en cada nivel.