El algoritmo de resolución de anchos
La ecuación de CSS 2.1 que gobierna el ancho de toda caja de bloque, qué pasa cuando está sobredeterminada, cómo se resuelven los márgenes automáticos y por qué min y max se aplican en una segunda pasada.
Detrás de cada caja de bloque hay una ecuación con siete términos que tiene que sumar exactamente el ancho del bloque contenedor. Está escrita en CSS 2.1, sección 10.3, y no ha cambiado en veinte años. Casi todo el comportamiento que atribuyes a la intuición del navegador es esta ecuación resolviéndose, incluido el caso en el que no tiene solución y el motor tiene que romper el empate por decreto.
- Escribir la ecuación de anchos y saber cuál de sus términos es la incógnita.
- Resolver el caso sobredeterminado y saber qué término se sacrifica.
- Explicar el reparto con uno y con dos márgenes automáticos.
- Describir la segunda pasada que aplica
min-inline-sizeymax-inline-size.
La ecuación
Para toda caja de bloque no reemplazada en flujo normal, la suma de estos siete valores debe ser igual al ancho del bloque contenedor:
margin-izq + borde-izq + padding-izq + width
+ padding-der + borde-der + margin-der = ancho del contenedor
De los siete, tres pueden valer auto: los dos márgenes y width. Los otros cuatro nunca son automáticos —un borde auto no existe, un relleno auto se computa a cero— así que la ecuación tiene entre cero y tres incógnitas. Cómo se resuelve depende de cuántas haya.
Si width es auto, gana width: se le asigna lo que sobre después de todo lo demás, y cualquier margen auto se computa a cero. Este es el caso por defecto de un div y por eso rellena el contenedor.
Si width tiene valor y los dos márgenes son auto, se reparte el sobrante a partes iguales. Es el centrado de toda la vida.
Si width tiene valor y solo un margen es auto, ese margen absorbe todo el sobrante. Es el patrón para empujar una caja a un lado sin conocer el ancho del contenedor.
Si width tiene valor y ninguno de los márgenes es auto, no hay incógnitas y la ecuación probablemente no cuadre. Ese es el caso interesante.
/* width auto: la caja rellena, los márgenes auto se anulan */
.a { inline-size: auto; margin-inline: auto; }
/* dos márgenes auto: centrado */
.b { inline-size: 40rem; margin-inline: auto; }
/* un margen auto: empuja a la derecha en escritura latina */
.c { inline-size: 40rem; margin-inline-start: auto; margin-inline-end: 0; }
El caso sobredeterminado
Cuando los siete términos están fijados y no suman el ancho del contenedor, la ecuación está sobredeterminada. La especificación no deja esto al azar: dicta que se ignora el valor calculado de margin-right y se recalcula para que la ecuación cuadre.
El detalle que importa es que “right” aquí es literal en modo horizontal de izquierda a derecha, pero la regla real está enunciada en función de la dirección: se sacrifica el margen del final del eje en línea. En un documento con direction: rtl, el margen que se ignora es el izquierdo.
.padre { inline-size: 500px; }
.hijo { inline-size: 400px; margin-inline: 100px; }
/* 100 + 400 + 100 = 600, no 500.
El motor recalcula el margen final a 0 y la caja queda pegada al final. */
Este comportamiento explica un síntoma que desconcierta: pones márgenes simétricos y la caja aparece descentrada. No es un bug de redondeo, es la ecuación descartando el término que la especificación le dijo que descartara.
Un margen negativo no es un caso especial: entra en la suma con su signo. Por eso margin-inline-start: -1rem en una caja de ancho auto la hace un rem más ancha que su contenedor, y por eso el viejo truco de los márgenes negativos para anular el relleno del padre funcionaba con precisión matemática.
Cuando no hay franja: el ajuste al contenido
La ecuación anterior asume que la caja tiene una franja de ancho reservada en el bloque contenedor. Los flotantes, los elementos con position: absolute y los inline-block no la tienen, y para ellos width: auto se resuelve con el ajuste al contenido:
resultado = min( max( min-content, disponible ), max-content )
Donde disponible es el ancho del bloque contenedor menos los márgenes, bordes y rellenos de la caja. Es la misma fórmula que define fit-content, y no es casualidad: fit-content es la palabra clave que expone este algoritmo.
Para los elementos absolutamente posicionados hay una capa más. Su ecuación incluye también inset-inline-start e inset-inline-end, con lo que puede haber hasta cinco incógnitas. Las reglas de resolución están en CSS 2.1 sección 10.3.7 y se resumen en dos casos útiles: si fijas los dos inset y inline-size: auto, la caja se estira entre ellos; si fijas los dos inset y un ancho, los márgenes automáticos centran la caja en esa franja.
/* Centrado absoluto sin transform ni cálculos */
.superpuesto {
position: absolute;
inset-inline: 0;
inline-size: 20rem;
margin-inline: auto;
}
La segunda pasada de min y max
Un punto que casi nadie tiene interiorizado: min-inline-size y max-inline-size no participan en la ecuación anterior. Se aplican después, y el algoritmo completo tiene tres pasadas.
Primero se resuelve la ecuación ignorando por completo min-inline-size y max-inline-size. Se obtiene un ancho tentativo.
Segundo, si ese ancho tentativo supera max-inline-size, se descarta y se repite toda la ecuación desde cero usando max-inline-size como si fuera el valor de inline-size.
Tercero, si el ancho resultante es menor que min-inline-size, se descarta otra vez y se repite la ecuación usando min-inline-size como valor de inline-size.
El orden implica una jerarquía firme: min-inline-size gana a max-inline-size, y ambos ganan a inline-size. Si escribes min-inline-size: 40rem; max-inline-size: 20rem, el resultado es 40rem, porque el mínimo se aplica el último y sobrescribe.
Que la ecuación se rehaga entera —y no solo se recorte el número— tiene una consecuencia práctica que sí notas: los márgenes automáticos se recalculan con el nuevo ancho. Por eso una caja con max-inline-size: 65ch y margin-inline: auto sigue centrada cuando el contenedor crece, en vez de quedarse pegada a un lado.
/* Centrado que aguanta cualquier ancho de viewport */
.contenido {
inline-size: auto;
max-inline-size: 65ch;
margin-inline: auto;
}
Fíjate en que inline-size: auto con max-inline-size es distinto de inline-size: 65ch: el primero se encoge por debajo de 65ch cuando el contenedor es estrecho, sin desbordar; el segundo desborda. Ese es todo el motivo de que las hojas de estilos serias usen topes en lugar de anchos.
El caso sobredeterminado —siete valores fijos que no suman lo que deben— es una de las decisiones más reveladoras de toda la especificación, porque muestra la restricción real bajo la que se diseñó CSS: no puede fallar. Un compilador ante un sistema de ecuaciones inconsistente lanza un error y se detiene. CSS no puede hacer eso: al otro lado hay un usuario esperando ver una página, y una página a medias es infinitamente peor que una página con un margen equivocado. Así que la especificación se ve obligada a algo que en un lenguaje de programación sería impensable: elegir arbitrariamente un término y sacrificarlo, y documentar esa arbitrariedad para que al menos sea la misma en todos los motores. Ese principio, recuperación garantizada por encima de corrección, recorre CSS de arriba abajo y explica cosas que si no parecen chapuzas: por qué una declaración con un valor inválido se descarta en silencio y no rompe la regla entera, por qué un selector desconocido invalida solo su bloque, por qué no hay excepciones ni logs. La consecuencia operativa es dura y conviene decirla claro: CSS no te avisa de tus errores porque no puede permitírselo. En cualquier otro lenguaje, un error se manifiesta como un fallo; aquí se manifiesta como una página que se ve un poco rara y que nadie relaciona con la línea que la causó. Por eso la disciplina de escribir restricciones en vez de números no es estilo: es la única forma de compensar la ausencia total de un compilador que te grite.