wandres.dev
LAYOUTS INTRÍNSECOS · Responsive sin breakpoints

El patrón RAM: repeat, auto, minmax

La galería que decide sola cuántas columnas tiene: cómo calcula el motor el número de pistas, la diferencia real entre auto-fit y auto-fill, y el desbordamiento que hay que arreglar siempre.

⏱ 18 min

Una sola declaración de grid-template-columns sustituye a cuatro media queries, se adapta a cualquier contenedor y no menciona el viewport en ningún sitio. Es el patrón más rentable de todo el CSS moderno, y también uno de los peor entendidos: casi todo el mundo lo copia sin saber cómo decide el motor el número de columnas, y por eso casi todo el mundo arrastra el mismo desbordamiento en pantallas estrechas.

🎯 Al terminar esta lección sabrás
  • Escribir el patrón RAM y explicar qué aporta cada una de sus tres piezas.
  • Calcular a mano cuántas pistas generará el motor para un contenedor dado.
  • Distinguir auto-fit de auto-fill por su efecto sobre las pistas vacías.
  • Corregir el desbordamiento en contenedores más estrechos que el mínimo declarado.

Las tres piezas

RAM son las iniciales de las tres funciones que lo componen: Repeat, Auto (auto-fit o auto-fill) y Minmax. El nombre lo popularizó Una Kravets y describe bien la estructura.

.galeria {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: 1rem;
}

repeat() genera pistas sin decir cuántas. auto-fit le dice al motor que el número lo decida él a partir del espacio. minmax(18rem, 1fr) fija las dos restricciones que gobiernan la decisión: ninguna columna baja de 18rem, y el sobrante se reparte a partes iguales.

Nada de esto es un truco. Es una consulta al algoritmo de dimensionado de pistas: le has dado un mínimo y le has pedido que quepan tantas como pueda.

Cómo calcula el motor el número de pistas

El cálculo es determinista y lo puedes hacer a mano, cosa que conviene para elegir el mínimo con criterio. Con un contenedor de ancho útil W, un mínimo M y un hueco G, el número de pistas es:

n = floor( (W + G) / (M + G) )     y como mínimo 1

El + G de arriba está porque hay un hueco menos que columnas: n columnas de M con n - 1 huecos de G ocupan n*M + (n-1)*G, y despejar n de esa desigualdad da la fórmula. Para W = 1000px, M = 288px (18rem con raíz de 16px) y G = 16px, sale floor(1016 / 304) = 3. Tres columnas de 322,67px cada una, porque el 1fr reparte los 1000px entre las tres tras descontar los dos huecos.

Dos consecuencias útiles. La primera: el hueco cuenta en el umbral, así que subir el gap reduce columnas antes de lo que esperas. La segunda: si quieres que un componente pase a dos columnas exactamente a partir de cierto ancho, el mínimo que debes declarar es algo menos de la mitad de ese ancho menos el hueco, y ya no necesitas una media query para conseguirlo.

auto-fit frente a auto-fill

Los dos generan el mismo número de pistas. La diferencia aparece después, y solo cuando hay menos elementos que pistas.

auto-fill mantiene las pistas vacías como pistas de pleno derecho: ocupan su espacio y el contenido se queda a la izquierda. auto-fit colapsa a cero las pistas vacías, y como los 1fr de las supervivientes vuelven a repartirse todo el ancho, el contenido se estira.

/* dos elementos en un contenedor de 1200px */
.rellena { grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); } /* 4 pistas: 2 llenas, 2 vacias */
.ajusta  { grid-template-columns: repeat(auto-fit,  minmax(18rem, 1fr)); } /* 4 pistas: 2 colapsadas, 2 enormes */

La elección tiene consecuencias de diseño reales. auto-fit con un solo elemento produce una tarjeta de 1200px de ancho, que casi nunca es lo que quieres para una tarjeta. auto-fill la deja a 18rem y el resto en blanco, que es lo que un diseñador esperaría. Como regla práctica: auto-fit cuando el número de elementos es alto y estable, auto-fill cuando puede ser bajo o variable. Si quieres auto-fit sin tarjetas gigantes, pon un tope al máximo del minmax:

.galeria { grid-template-columns: repeat(auto-fit, minmax(18rem, 26rem)); justify-content: center; }

Ojo con ese caso: sin 1fr las pistas ya no absorben el sobrante, así que necesitas justify-content para decidir qué se hace con el espacio libre del contenedor.

El desbordamiento que hay que arreglar siempre

El patrón tal cual falla cuando el contenedor es más estrecho que el mínimo. Con minmax(18rem, 1fr) en un contenedor de 15rem, el motor genera una pista de 18rem porque el mínimo es un mínimo duro, y el contenido desborda. Pasa en móviles pequeños, en columnas laterales estrechas y en cualquier sitio donde el usuario haya ampliado el tamaño de letra.

La corrección es meter un techo en el propio mínimo:

.galeria {
  --minimo: 18rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(var(--minimo), 100%), 1fr));
  gap: 1rem;
}

min(18rem, 100%) vale 18rem mientras quepa y vale el ancho del contenedor cuando no cabe, con lo que la pista nunca es más ancha que su contenedor. La custom property no es adorno: convierte el componente en configurable desde fuera sin duplicar la regla.

.galeria.densa   { --minimo: 12rem; }
.galeria.holgada { --minimo: 24rem; }
🛑
Por qué no basta con overflow-x: hidden

Tapar el desbordamiento con overflow no arregla nada: el contenido sigue midiendo más que su caja, simplemente deja de verse. Además overflow distinto de visible crea un contexto de formato de bloque y puede romper un position: sticky que dependa de un ancestro desplazable. La corrección correcta actúa sobre el tamaño, no sobre la visibilidad.

Estás declarando un invariante, y el motor lo mantiene mejor que tú

Lo que hace especial a este patrón no es que ahorre líneas, es que cambia la naturaleza de lo que está escrito. minmax(18rem, 1fr) es un invariante: “ninguna tarjeta de esta galería medirá nunca menos de 18rem, y el espacio sobrante se repartirá por igual”. El motor lo revalida en cada cálculo de layout, y lo revalida en situaciones que tú no probaste nunca: con la fuente ampliada al 200%, dentro de un contenedor que aún no existía cuando escribiste la regla, en una ventana partida de un plegable, con un idioma cuyas palabras no caben. Un juego de media queries no puede darte esa garantía porque no expresa ninguna: expresa cuatro fotografías de estados concretos, y entre fotografía y fotografía el comportamiento es lo que salga. La diferencia es la misma que hay entre una aserción y un test: la aserción vale para todas las entradas, el test vale para las que se te ocurrieron. Cuando puedas elegir entre escribir el resultado y escribir la restricción que lo produce, escribe la restricción; es la única forma de que el CSS siga siendo correcto en escenarios que no has imaginado.

⚔️ Domina el umbral
  1. Calcula a mano cuántas columnas genera repeat(auto-fill, minmax(20rem, 1fr)) con gap: 2rem en un contenedor de 1280px, y comprueba el resultado en el inspector de rejilla.
  2. Reduce el contenedor a 200px y observa el desbordamiento; aplica la corrección con min() y verifica que desaparece.
  3. Monta la misma galería con dos elementos y compara auto-fit y auto-fill lado a lado.
  4. Expón el mínimo como custom property y crea dos variantes de densidad sin duplicar la declaración de pistas.
  5. Sustituye el máximo 1fr por 26rem y decide qué valor de justify-content corresponde a tu diseño.