wandres.dev
GRID I · El sistema de dos dimensiones

La unidad fr y qué significa exactamente

fr no es un porcentaje ni una fracción del ancho total: es una fracción del espacio libre restante. Por qué 1fr 1fr no siempre da columnas iguales y qué hace falta para que las dé.

⏱ 18 min

La unidad fr se explica normalmente como “una fracción del espacio disponible”, y esa definición es lo bastante imprecisa como para producir el bug más común de grid: dos pistas 1fr 1fr que no salen iguales. La definición exacta tiene dos partes que cambian el resultado: es una fracción del espacio libre restante, y 1fr es en realidad una abreviatura de minmax(auto, 1fr). Conocer esas dos partes convierte el comportamiento de fr en algo que puedes calcular.

🎯 Al terminar esta lección sabrás
  • Definir fr con precisión y calcular un reparto a mano.
  • Explicar por qué 1fr 1fr puede dar pistas de anchos distintos.
  • Usar minmax(0, 1fr) y saber qué renuncias al hacerlo.
  • Distinguir fr de un porcentaje en un contenedor con gaps.

La definición exacta

Una pista con Nfr recibe una parte del espacio libre proporcional a N. El espacio libre es lo que queda del contenedor después de restar los gaps y el tamaño de todas las pistas que no son flexibles.

espacio libre = tamaño del contenedor
              - suma de gaps
              - suma de las pistas no flexibles ya resueltas

Contenedor de 1000px, gap: 20px, tres pistas: 200px 1fr 2fr. Hay dos gaps, 40px. La pista fija ocupa 200px. Espacio libre: 1000 − 40 − 200 = 760. La suma de factores es 3, así que 1fr recibe 253,33 y 2fr recibe 506,67.

Es exactamente el mismo mecanismo que flex-grow, y la analogía es útil: fr es a las pistas lo que flex-grow es a los elementos. La diferencia está en el mínimo, y ahí es donde aparece el problema.

📝
fr solo puede ir en el máximo

minmax(1fr, 200px) es sintaxis inválida: una unidad flexible no puede ser la función mínima de dimensionado de una pista. La razón es circular: para saber cuánto vale un fr hay que conocer el espacio libre, y para conocer el espacio libre hay que haber resuelto los mínimos. La especificación rompe el ciclo prohibiendo fr en la posición del mínimo.

Por qué 1fr 1fr no siempre da pistas iguales

Aquí está el punto que cuesta encontrar en la documentación. 1fr no es minmax(0, 1fr): es minmax(auto, 1fr).

Ese auto en la posición del mínimo significa que la pista nunca será más estrecha que la contribución mínima de su contenido. Y esa contribución mínima incluye el tamaño mínimo automático de los elementos grid que la ocupan, es decir, el min-content de su contenido.

<div class="dos">
  <div>Corto</div>
  <div>supercalifragilisticoespialidoso</div>
</div>
.dos { display: grid; grid-template-columns: 1fr 1fr; inline-size: 400px; }

Si esa palabra larga mide 300px en min-content, la segunda pista no puede bajar de 300px. El reparto flexible se calcula sobre lo que queda, y el resultado son dos pistas de 100px y 300px. No es un bug: es el auto del mínimo haciendo su trabajo.

La corrección es explícita:

.dos { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

Ahora el mínimo es cero, las pistas salen a 200px cada una, y el contenido de la segunda desborda o se parte según lo que tenga configurado. Has cambiado un problema de tamaño de pista por un problema de desbordamiento de contenido, que es un intercambio consciente y a menudo el correcto.

/* La versión completa: pistas iguales y contenido que se parte */
.dos {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.dos > * { overflow-wrap: break-word; }

Dicho lo cual, sería un error convertir minmax(0, 1fr) en un reflejo. El auto del mínimo es la protección que evita que el contenido desaparezca, exactamente igual que el tamaño mínimo automático de flexbox, y en muchos layouts es lo correcto.

En una rejilla de tarjetas con textos parecidos, el mínimo automático nunca se activa porque ninguna contribución supera el reparto flexible. Ahí 1fr y minmax(0, 1fr) dan el mismo resultado y el primero se lee mejor.

En una rejilla donde una pista puede recibir contenido no partible —código, tablas, imágenes— el mínimo automático te avisa de que hay un problema real ensanchando la pista. Sustituirlo por cero esconde el aviso y produce un desbordamiento silencioso.

La regla: usa minmax(0, 1fr) cuando necesites que las proporciones se respeten por encima del contenido, y 1fr cuando el contenido tenga derecho a defender su espacio.

fr frente a porcentaje

Un porcentaje en una pista de grid se resuelve contra el tamaño del contenedor sin descontar los gaps. Un fr opera sobre el espacio libre, que sí los descuenta. La diferencia se ve inmediatamente:

/* Desborda: 50 + 50 = 100% más los 20px del gap */
.a { display: grid; grid-template-columns: 50% 50%; gap: 20px; }

/* Cuadra: los gaps se restan antes de repartir */
.b { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }

Esa es la razón por la que fr sustituyó a los porcentajes en la práctica y por la que las rejillas de grid no necesitan la aritmética de márgenes negativos de los sistemas de rejilla antiguos. calc(50% - 10px) funcionaba, pero había que recalcularlo cada vez que cambiaba el número de columnas o el gap.

Otra diferencia: fr puede combinarse con pistas de tamaño intrínseco sin que aparezcan ciclos. grid-template-columns: max-content 1fr es válido y produce lo que esperas: la primera pista mide su contenido, la segunda se lleva el resto. Con porcentajes eso no se puede expresar.

/* Etiqueta que mide lo que mide, valor que ocupa el resto */
.pareja {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.5rem 1rem;
}

Valores fraccionarios y factores menores que uno

fr acepta decimales, y hay una regla especial cuando la suma de todos los factores es menor que 1: en ese caso cada pista recibe su fracción del espacio libre y el resto queda sin repartir.

/* Dos pistas que se llevan el 30% y el 20% del espacio libre.
   El 50% restante queda como hueco al final. */
.parcial { display: grid; grid-template-columns: 0.3fr 0.2fr; }

Es la misma regla que en flex-grow y sirve para lo mismo: repartir parcialmente sin recurrir a una pista extra. Se usa poco, pero cuando aparece en código ajeno conviene reconocerla en lugar de tomarla por un error de escritura.

fr existe porque el porcentaje era la abstracción equivocada para el layout

Merece la pena detenerse en por qué hizo falta inventar una unidad nueva en 2011 cuando los porcentajes llevaban quince años funcionando. El problema del porcentaje es que se resuelve contra un total que el autor no controla del todo: descuenta lo que el navegador considere que debe descontar, y no sabe nada de los gaps, de los bordes ni de las pistas hermanas. Eso convierte cualquier rejilla en un ejercicio de aritmética defensiva, y es exactamente lo que hacían Bootstrap y los sistemas de rejilla de su generación: columnas del 8,333% con padding compensatorio y márgenes negativos en el contenedor para anular el padding de los extremos. Miles de líneas para simular una unidad que no existía. fr no es azúcar sobre el porcentaje: es una unidad de reparto de un resto, un concepto que el porcentaje no puede expresar porque un porcentaje siempre necesita conocer su base de antemano. Y el patrón se repite en toda la evolución reciente del lenguaje: gap existe porque el margen no podía expresar “espacio entre, pero no alrededor”; clamp() existe porque min-width y max-width no podían expresar “prefiero esto”; auto-fit existe porque repeat con un número no podía expresar “los que quepan”. Cada vez que un patrón exige aritmética repetitiva y frágil, lo que falta no es una técnica mejor: falta una primitiva. Reconocer esa carencia en tu propio código es lo que separa a quien escribe utilidades de quien diseña abstracciones.