wandres.dev
FLEXBOX EN LA PRÁCTICA · Alineación, orden y patrones

flex-wrap y el límite real de flexbox

Cómo se reparten los elementos en líneas, por qué las líneas son independientes entre sí, por qué eso hace imposible alinear entre filas y qué significa exactamente que flexbox sea unidimensional.

⏱ 18 min

flex-wrap: wrap convierte una fila en varias, y con eso parece que flexbox pueda hacer rejillas. No puede, y el motivo no es una limitación de implementación sino una consecuencia directa del algoritmo: cada línea resuelve su reparto de espacio por separado, sin saber nada de las demás. Entender exactamente dónde está ese muro es lo que te dice cuándo dejar flexbox y pasarte a grid, sin necesidad de un árbol de decisión memorizado.

🎯 Al terminar esta lección sabrás
  • Describir el algoritmo de reparto en líneas y en qué orden ocurre.
  • Explicar por qué las columnas de un flex con wrap no se alinean entre filas.
  • Usar align-content correctamente y saber cuándo no hace nada.
  • Reconocer el problema del último elemento huérfano y sus soluciones.

Cómo se forman las líneas

El reparto en líneas ocurre antes que el algoritmo de crecimiento y encogimiento, y ese orden lo explica todo.

El motor recorre los elementos en orden y va acumulando sus tamaños hipotéticos más los gaps. En cuanto la suma supera el tamaño del contenedor en el eje principal, cierra la línea antes de ese elemento y empieza una nueva. Un elemento cuyo tamaño hipotético ya supere por sí solo el contenedor ocupa su propia línea.

Después, y solo después, se ejecuta el bucle de resolución de longitudes flexibles una vez por línea, con el espacio libre de esa línea concreta.

.rejilla {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
.rejilla > * { flex: 1 1 15rem; }

Con seis elementos en un contenedor de 50rem, entran tres por línea. Cada una de las dos líneas reparte por su cuenta: si las dos tienen tres elementos, salen iguales; si la segunda tiene dos, esos dos son más anchos que los tres de arriba.

📝
flex-wrap: wrap-reverse

Existe un tercer valor. wrap-reverse invierte el eje transversal: las líneas nuevas se apilan hacia el inicio en lugar de hacia el final. Intercambia cross-start con cross-end, así que también invierte el significado de align-items: flex-start. Se usa poquísimo y cuando aparece suele ser un accidente.

El muro: las líneas no se hablan

Aquí está el límite estructural. Cada línea calcula su espacio libre y lo reparte entre sus propios elementos. No hay ningún mecanismo por el que la primera línea sepa cuántos elementos tiene la segunda ni qué anchos han resultado.

La consecuencia práctica es que no puedes alinear verticalmente el segundo elemento de la primera fila con el segundo de la segunda. Si sus contenidos tienen tamaños distintos, sus anchos serán distintos, y la retícula visual no existe.

<div class="mal">
  <div>Corto</div>
  <div>Un texto bastante más largo que el anterior</div>
  <div>Medio texto</div>
  <div>Otro</div>
</div>
.mal { display: flex; flex-wrap: wrap; gap: 1rem; }
.mal > * { flex: 1 1 12rem; }

Con dos elementos por fila, los de la primera se reparten según sus contenidos y los de la segunda según los suyos. Las columnas no coinciden. Ningún valor de ninguna propiedad de flexbox lo arregla, porque la información necesaria no está disponible en el momento en que se toma la decisión.

Esto es lo que significa que flexbox sea unidimensional: gestiona un eje a la vez. Con wrap hay varias líneas, pero cada una sigue siendo un problema de un eje resuelto de forma aislada. Grid es bidimensional porque resuelve filas y columnas en un único sistema donde una pista de columna es la misma para todas las filas.

/* Lo mismo, con retícula real */
.bien {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  gap: 1rem;
}

La regla de decisión que se deriva de esto es corta: si necesitas que las cosas se alineen en los dos ejes, es grid. Si solo te importa un eje y el otro es consecuencia, flexbox está bien y suele ser más simple.

align-content y su condición

align-content distribuye las líneas dentro del eje transversal. Sus valores son los mismos que los de justify-content más stretch, que es el inicial.

Solo tiene efecto si se cumplen dos condiciones a la vez: que haya flex-wrap: wrap y que el contenedor tenga más tamaño en el eje transversal del que ocupan las líneas. Con una sola línea, o con un contenedor cuya altura sea exactamente la de sus líneas, no hay nada que distribuir.

.panel {
  display: flex;
  flex-wrap: wrap;
  block-size: 30rem;          /* imprescindible: sin altura no sobra espacio */
  align-content: space-between;
  gap: 1rem;
}

El valor inicial stretch merece atención: estira las líneas para llenar el contenedor, repartiendo el sobrante entre ellas. Un contenedor alto con dos líneas de wrap tendrá líneas más altas de lo que su contenido pide, y los elementos con align-items: stretch se estirarán con ellas. Si eso no es lo que quieres, align-content: flex-start es lo que buscas, y es una de las declaraciones que más veces arregla “por qué mis tarjetas son tan altas”.

El último elemento huérfano

Un problema estético clásico. Con flex: 1 1 <base> y wrap, si la última línea tiene un solo elemento, ese elemento crece hasta ocupar todo el ancho y queda visualmente descolgado del resto.

/* Cinco tarjetas, tres por fila: la cuarta y la quinta salen enormes */
.tarjetas { display: flex; flex-wrap: wrap; gap: 1rem; }
.tarjetas > * { flex: 1 1 18rem; }

Hay tres soluciones y ninguna es perfecta.

La primera es no dejar crecer: flex: 0 1 18rem. Las tarjetas conservan su ancho base y la última línea queda alineada al inicio, con hueco a la derecha. Es honesto y funciona, pero deja un hueco visible.

La segunda es poner un tope: flex: 1 1 18rem; max-inline-size: 24rem;. El elemento huérfano crece hasta el tope y no más. Sigue quedando hueco, pero menos brusco.

La tercera es usar grid, donde el problema no existe porque las pistas son las mismas en todas las filas:

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

Este caso concreto —tarjetas que envuelven— es probablemente el motivo más común por el que un layout empieza en flexbox y acaba en grid. No es que flexbox esté mal usado: es que el requisito real era bidimensional desde el principio.

Que flexbox sea unidimensional no es una carencia, es lo que le permite ser intrínseco

Es fácil leer la unidimensionalidad de flexbox como una limitación que grid vino a superar, y ese encuadre lleva a la conclusión equivocada de que grid es flexbox pero mejor. La realidad es que las dos propiedades están acopladas: flexbox puede dejar que el contenido determine el tamaño precisamente porque no tiene que coordinar dos ejes. Cuando cada línea se resuelve por su cuenta, el motor puede consultar el tamaño intrínseco de cada elemento y darle exactamente lo que pide, porque nadie más depende de esa decisión. Grid no puede permitírselo: una pista de columna es compartida por todas las filas, así que su tamaño tiene que ser un compromiso entre los contenidos de todas ellas, y por eso el dimensionado de pistas de grid es un algoritmo de tres fases mucho más caro. La elección entre los dos modelos no es “unidimensional o bidimensional” sino quién manda sobre el tamaño. Flexbox le da la autoridad al contenido y a cambio renuncia a la retícula. Grid le da la autoridad a la retícula y a cambio los contenidos se acomodan. Por eso el criterio de decisión que de verdad funciona no es contar ejes, es contestar a esta pregunta: si dos elementos de este layout tienen contenidos de tamaños muy distintos, ¿prefiero que ocupen anchos distintos o que se alineen? Si prefieres que se alineen, ya estás en grid aunque solo tengas una fila.