wandres.dev
UNIDADES DE CONTENEDOR · cqw, cqi, cqmin y compañía

Contra qué se resuelven las unidades de contenedor

La cadena de resolución por eje, el fallback silencioso al viewport pequeño, por qué un contenedor no es su propia referencia y cómo depurar un valor en cqi que no cuadra.

⏱ 16 min

Una unidad de contenedor no falla nunca: siempre produce un número. Ese es exactamente el problema. Cuando no hay ningún contenedor elegible, cqi no es un error de sintaxis ni deja la propiedad sin aplicar; se convierte silenciosamente en una unidad de viewport, y el resultado se parece lo bastante a lo que querías como para que no lo notes hasta que alguien coloca el componente en un sitio estrecho. Conocer la cadena de resolución completa es lo que convierte estas unidades en algo fiable.

🎯 Al terminar esta lección sabrás
  • Describir la cadena de resolución de cqi y la de cqb, que no son la misma.
  • Explicar por qué el fallback al viewport pequeño es peligroso precisamente por ser razonable.
  • Justificar que un contenedor no sea su propia referencia.
  • Depurar un valor en unidades de contenedor que no coincide con lo esperado.

La cadena, eje por eje

Las unidades se resuelven por eje y buscan contenedores distintos según el eje.

flowchart TB
A[Un valor usa cqi o cqw] --> B{Hay un ancestro con container-type inline-size o size}
B -->|Si| C[1cqi es el uno por ciento de su tamano en linea]
B -->|No| D[1cqi equivale a 1svi es decir el viewport pequeno]
E[Un valor usa cqb o cqh] --> F{Hay un ancestro con container-type size}
F -->|Si| G[1cqb es el uno por ciento de su tamano de bloque]
F -->|No| H[1cqb equivale a 1svb]
style C fill:#a6e3a1,color:#11111b
style G fill:#a6e3a1,color:#11111b
style D fill:#f9e2af,color:#11111b
style H fill:#f9e2af,color:#11111b
style A fill:#89b4fa,color:#11111b
style E fill:#89b4fa,color:#11111b

La consecuencia interesante es que los dos ejes pueden resolverse contra contenedores distintos. Si tienes un panel con container-type: size y dentro una tarjeta con container-type: inline-size, un elemento dentro de la tarjeta resolverá cqi contra la tarjeta y cqb contra el panel, porque la tarjeta no es elegible para el eje de bloque. No es un fallo: es la definición, y es coherente con que cada tipo de containment solo garantice el eje que garantiza.

Tres comportamientos que sorprenden

El fallback que hace daño

Cuando no hay contenedor, 1cqi vale 1svi: el 1% del tamaño en línea del viewport pequeño, es decir, del viewport con la barra de direcciones desplegada en móvil.

Esto está diseñado para que las unidades sigan produciendo algo razonable en lugar de romper el layout, y en ese objetivo acierta. El problema es de diagnóstico. Un componente al que se le olvidó el container-type en el envoltorio no se ve roto: se ve casi bien, porque en una portada a ancho completo el contenedor y el viewport miden aproximadamente lo mismo. El fallo solo aparece cuando alguien coloca ese componente en una barra lateral de 300px, y entonces los paddings y los títulos siguen dimensionados como si tuvieran toda la pantalla.

/* el envoltorio se quedo sin container-type: nadie lo nota en la portada */
.tarjeta-wrap { /* falta: container: tarjeta / inline-size; */ }

.tarjeta { padding: 4cqi; }   /* = 4svi: el 4% de la VENTANA */

Este es un argumento fuerte a favor de una disciplina concreta: declara el contenedor y usa las unidades en el mismo fichero. Si el container-type vive en una hoja de layout global y las cqi en la hoja del componente, la relación entre los dos se pierde de vista y el fallo se hace invisible.

💡
Detección rápida en el inspector

Selecciona el elemento que usa la unidad y mira el valor calculado de la propiedad, no el declarado. Si padding: 4cqi aparece calculado como un valor que no es el 4% del ancho de tu contenedor, o no hay contenedor o no es del tipo que crees. Los navegadores modernos también marcan qué elementos son contenedores de consulta en el árbol del inspector.

Un contenedor no es su propia referencia

La regla es la misma que para @container: la referencia es el ancestro más cercano que sea contenedor elegible, nunca el propio elemento. Un elemento que declara container-type: inline-size y usa cqi en sus propias propiedades está midiendo contra su contenedor de más arriba, no contra sí mismo.

.panel {
  container: panel / inline-size;
  padding: 3cqi;      /* OJO: 3% del contenedor DE .panel, no de .panel */
}

Es la misma prohibición y por la misma razón: si padding en cqi se midiera contra el propio elemento, el padding entraría en el cálculo del tamaño del elemento, que a su vez determina el padding. El motor no puede resolver eso, así que la especificación lo declara inexpresable.

En la práctica esto refuerza el patrón del envoltorio que ya usas para las consultas: el envoltorio declara el contenedor, el componente de dentro usa las unidades, y la relación es evidente al leer el CSS.

Custom properties y unidades de contenedor

Una custom property sin registrar guarda una secuencia de tokens, no un valor calculado. Eso significa que --espacio: 3cqi no se resuelve donde se declara: se resuelve donde se usa, contra el contenedor del elemento que la usa.

.componente { --espacio: 3cqi; }
.componente .zona { padding: var(--espacio); }        /* contra el contenedor de .zona */

Mientras no haya otro contenedor entre medias, los dos elementos comparten referencia y el resultado es el que esperas. Si un descendiente declara su propio container-type, sus hijos resolverán la misma custom property contra un contenedor distinto y obtendrás dos valores diferentes de la misma variable en la misma página. No es un bug: es la sustitución de tokens funcionando como está especificada, pero es exactamente el tipo de comportamiento que conviene conocer antes de encontrárselo.

Registrar la propiedad con @property y un syntax de longitud cambia esta dinámica, porque el valor pasa a computarse en el elemento donde se declara. Si necesitas que el valor sea el mismo para todo el subárbol, registrarla es la forma de garantizarlo.

Método de depuración

Cuando una unidad de contenedor no da el número esperado, cuatro comprobaciones en este orden resuelven prácticamente todos los casos.

Primero, ¿existe un ancestro con container-type? Si no, estás midiendo el viewport.

Segundo, ¿el tipo cubre el eje que usas? cqb contra un inline-size no cuenta.

Tercero, ¿el ancestro más cercano es el que crees? Puede haber otro contenedor declarado por una hoja de librería o por un layout genérico entre tu componente y el que tú declaraste. La solución es nombrar y usar el nombre.

Cuarto, ¿el contenedor tiene el ancho que crees? Un contenedor con containment dentro de un flex sin base explícita puede haber colapsado, y entonces todos tus cqi valen casi cero.

Un fallback razonable es peor que un fallo cuando la diferencia importa

La decisión de que las unidades de contenedor caigan al viewport en vez de invalidar la declaración parece obviamente correcta y merece pensarse dos veces, porque encierra un compromiso que aparece constantemente en el diseño de APIs. Un fallo ruidoso —la declaración se descarta, el layout se ve mal— se detecta en la primera prueba y se arregla en dos minutos. Un fallback razonable produce un resultado plausible que sobrevive a la revisión de código, al diseño y a las pruebas manuales, y solo se manifiesta en la combinación de circunstancias que nadie probó: el componente en la barra lateral, el idioma con palabras largas, la pantalla estrecha. La especificación eligió el fallback porque el coste de romper páginas existentes es inaceptable, y esa es una razón legítima. Pero para ti, como quien construye encima, la conclusión operativa es la contraria a la que sugiere la comodidad: cuando una plataforma te da un valor por defecto silencioso, tu trabajo es reintroducir el ruido, con una convención, una comprobación en el inspector o una regla de linter. Los sistemas que fallan en silencio no producen menos errores; producen errores que se descubren mucho más tarde y mucho más lejos de su causa.