Qué es un contexto de formato
El concepto que la especificación da por sabido y ningún tutorial explica: el entorno de maquetación que gobierna a un grupo de cajas, qué caja lo establece y por qué eso decide el comportamiento de todo tu layout.
Cuando una caja se comporta de una forma que no entiendes, casi nunca es un bug del navegador: es que estás mirando la caja equivocada. El layout de CSS no se resuelve elemento a elemento, sino por contextos de formato: entornos independientes, cada uno con sus propias reglas de colocación, de márgenes y de flotantes. La especificación lleva desde 1998 hablando de esto y casi ningún tutorial lo menciona. De ahí que media docena de comportamientos perfectamente deterministas parezcan magia negra.
- Definir qué es un contexto de formato y qué caja lo establece.
- Separar el tipo de display externo del interno y leer
displaycomo dos valores. - Explicar qué significa que un contexto de formato sea independiente.
- Situar cualquier caja de un documento dentro del contexto que la gobierna.
El flujo normal no es una sola cosa
La expresión “flujo normal” suena a un único mecanismo: las cajas de bloque se apilan de arriba abajo, las cajas en línea se colocan de izquierda a derecha. En realidad son dos algoritmos distintos, el de bloque y el de línea, y la especificación los llama contexto de formato de bloque (BFC) y contexto de formato en línea (IFC).
Un contexto de formato es el entorno en el que se maqueta un conjunto de cajas hermanas. Determina tres cosas: en qué dirección se colocan, cómo interactúan sus márgenes y qué ven de los elementos flotados. Y hay un detalle que lo cambia todo: la caja que establece un contexto de formato no vive dentro de él. Un div con overflow: hidden establece un BFC para sus hijos, pero él mismo sigue participando en el BFC de su padre. Por eso las reglas que se aplican al contenedor y las que se aplican al contenido son distintas, y por eso confundirlas produce diagnósticos absurdos.
Los contextos de formato modernos se han multiplicado. Además del BFC y el IFC, existen el de flex, el de grid, el de tabla, el de multicolumna y el de ruby. Cada uno define un algoritmo de colocación distinto para sus participantes directos.
El display tiene dos mitades
La especificación CSS Display 3 formalizó algo que llevaba veinte años implícito: display no es una lista arbitraria de palabras clave, sino la combinación de un tipo externo y un tipo interno.
El tipo externo dice cómo participa la caja en el contexto de su padre: block o inline. El tipo interno dice qué contexto de formato establece la caja para sus hijos: flow, flow-root, flex, grid, table, ruby.
| valor clásico | equivale a | participa como | crea para sus hijos |
|---|---|---|---|
display: block |
block flow |
bloque | flujo normal, sin contexto nuevo |
display: inline-block |
inline flow-root |
línea | un BFC nuevo |
display: flex |
block flex |
bloque | contexto de flex |
display: inline-flex |
inline flex |
línea | contexto de flex |
display: grid |
block grid |
bloque | contexto de grid |
display: flow-root |
block flow-root |
bloque | un BFC nuevo |
Leer la tabla de derecha a izquierda es una revelación pequeña: inline-block nunca fue un tipo especial de caja, sino la combinación de “participa en línea” con “crea un contexto de bloque propio”. Y flow-root es exactamente la mitad interesante de inline-block sin la mitad molesta.
/* Estas dos declaraciones son idénticas para el motor */
.a { display: inline-block; }
.a { display: inline flow-root; }
La sintaxis de dos valores está disponible en los motores desde 2023 y sigue siendo minoritaria en código real, pero como modelo mental es la mejor herramienta que tienes: cada vez que escribas un display, pregúntate qué mitad estás cambiando.
La diferencia entre display: block y display: flow-root es exactamente una palabra en el tipo interno, y es la que separa “mis hijos siguen en el contexto de mi abuelo” de “mis hijos viven en un contexto mío”. Todo el nivel 14 gira alrededor de esa palabra.
Independiente quiere decir aislado
CSS Display 3 introdujo el término contexto de formato independiente. Una caja que establece uno se compromete a tres cosas frente al exterior:
Primero, ningún flotante de fuera penetra en su interior, y ningún flotante de su interior sobresale. El aislamiento es en las dos direcciones.
Segundo, sus márgenes no colapsan con los de sus hijos. El colapso de márgenes solo ocurre entre cajas del mismo contexto de bloque, así que establecer uno nuevo lo corta de raíz.
Tercero, y como consecuencia de lo primero, la caja se dimensiona para contener a sus propios flotantes. La altura deja de ignorarlos.
Todos los contextos de formato modernos son independientes: flex, grid, tabla, multicolumna, flow-root. El único que no lo es por defecto es el flujo de bloque corriente, display: block, que es precisamente el caso de la inmensa mayoría de los elementos de un documento. De ahí que todos los bugs clásicos ocurran ahí.
/* Un contenedor de bloque corriente: NO es independiente.
Sus hijos comparten el BFC del abuelo. */
.contenedor { display: block; }
/* El mismo contenedor, ahora independiente. Nada cruza el borde. */
.contenedor { display: flow-root; }
El árbol de contextos
Un documento no tiene un contexto de formato: tiene un árbol de ellos. El elemento raíz establece el BFC inicial, que abarca todo el viewport. Dentro, cada caja que cumple una de las condiciones del catálogo abre un contexto hijo, y las cajas que no la cumplen simplemente heredan el contexto en el que están.
Ese árbol no coincide con el árbol del DOM, y tampoco coincide con el árbol de contextos de apilamiento ni con la cadena de bloques contenedores. Son tres estructuras distintas que se calculan con reglas distintas sobre el mismo DOM, y la mayor parte de la confusión con CSS viene de tratarlas como si fueran una sola.
<body> <!-- BFC inicial -->
<article> <!-- sigue en el BFC inicial -->
<aside style="overflow: auto"> <!-- abre un BFC propio -->
<p>Texto</p> <!-- vive en el BFC del aside -->
</aside>
<p>Texto</p> <!-- vive en el BFC inicial -->
</article>
</body>
Los dos párrafos del ejemplo tienen el mismo aspecto y la misma profundidad relativa, pero uno puede colapsar márgenes con body y el otro no. Ninguna propiedad del párrafo explica la diferencia: la explica el contexto en el que cada uno está.
La razón por la que CSS le parece impredecible a un ingeniero que viene de otros lenguajes es que intenta razonar sobre una propiedad y su valor, cuando el modelo real es de ámbitos anidados. Un contexto de formato es a la maquetación lo que un scope léxico es a un lenguaje de programación: un límite dentro del cual ciertas interacciones son posibles y fuera del cual sencillamente no existen. El colapso de márgenes no es una regla caprichosa, es una interacción que solo está definida entre participantes del mismo BFC. Un flotante no “se escapa”: es que el algoritmo de línea que lo esquiva está definido para un ámbito, y si nadie abre uno nuevo, ese ámbito llega hasta donde llegue. Cuando dejas de preguntar “qué propiedad arregla esto” y empiezas a preguntar “en qué ámbito está esta caja y quién lo abrió”, los bugs de layout pasan de ser aleatorios a ser triviales. Es el mismo salto mental que da un programador cuando entiende que una variable no tiene un valor mágico, sino un enlace en un entorno concreto.
- Coge cualquier página que hayas hecho y dibuja su árbol de contextos de formato, no su árbol de DOM.
- Para cada nodo del árbol, anota qué lo abrió: la raíz, un
overflow, un flex, un grid. - Traduce todos los
displayde esa hoja a la sintaxis de dos valores y comprueba en el navegador que el resultado es idéntico. - Localiza dos elementos hermanos visualmente que estén en contextos distintos, como el ejemplo del
aside.