Space-between, por favor

Le he dado tantas vueltas a este tema que al final me he decidido a hacer un artículo al respecto con mis impresiones para ver si alguien tiene el mismo problema y me da alguna solución mejor.

De todos es sabido, en los últimos años, que Flexbox es la mejor manera hoy en día para maquetar interfaces web. Se puede aplicar en muchas soluciones, pero una de las más útiles, al menos para mi, es a la hora de crear un grid, cuadrícula o rejilla.

En esta aplicación una de las cosas más guays de Flexbox es que los items se distribuyan de una forma homogenea dentro de un contenedor. Basta con decirle al contenedor, por ejemplo un ul, que tenga display: flex y utilizar el justify-content que mejor nos venga. Así el propio algoritmo de Flexbox calculará la posición de los items dentro del padre.

Desde hace un par de años que vengo aplicando Flexbox, me he hecho muy fan de esta manera de maquetar y sus posibilidades. Una de las propiedades de Flexbox que me parece muy potente es el justify-content: space-between, el cual distribuye automágicamente los items aprovechando todo el ancho de su contenedor. Sin embargo hay un pequeño problema con el que siempre me solía pegar y no encontraba solución, a continuación lo describo.

Con space-between no hace falta calcular el margen de los items, si no que se les asigna automágicamente. Solo has de pensar en dar el tamaño adecuado a los items para que entren los que deseas en cada fila y sobre algo de espacio entre ellos.

De esta forma, si por ejemplo tenemos una lista de posts y queremos mostrarlos en filas de 4 (o en 4 columnas, como se quiera expresar), solo hay que tener en cuenta que, para que entren 4 por fila, han de ser de, como mucho, un 25%. Si les asignamos un flex-basis de 24%, por ejemplo, van a entrar 4 en cada fila y sobrará un 4% del espacio para los márgenes. Así el primero estará a la izquierda del todo del contenedor, el cuarto a la derecha del todo y los 2 intermedios quedarán distribuidos equitativamente en el medio, dejando un margen igual entre cada item:

24% x 4 items = 96%, el 4% restante se divide entre 3 márgenes = 1,333% entre cada uno

Hasta ahí todo perfecto, somos felices y pensamos: «vaya como mola Flexbox, ahora maqueto en un plis-plas sin preocuparme de calcular márgenes, ¡ya los calcula él!».

Hasta que se nos da el caso de tener un número de items que no rellena todas las filas de este contenedor, que no sea múltiplo de 4, en este caso.

Si, por ejemplo, solo se cargan 6 items, entonces nos encontramos con que en la última fila solo hay 2 items, y estos se colocan uno en cada extremo de su padre. Ya que, en lugar de repartirse entre items un 4% del espacio, como en las otras filas, se reparte un 48%, ya que este es el espacio que queda libre. Y se quedan uno a cada lado, por que «space-between» hace que se repartan el espacio sobrante entre ellos, no por delante ni por detrás, sino entre uno y otro, eso es space between, literal.

Si en esta última fila hay 3 items no queda tan mal, el item de en medio se queda centrado, al repartir el espacio sobrante entre los 3:

Pero como la situación habitual es que nuestra carga de contenidos sea dinámica y no podamos controlar ni asegurar que siempre tengamos un numero concreto de items en un listado, no nos vale de mucho. En la mayoría de los casos queda bastante mal, hace un efecto visual extraño, aunque todo depende, como casi siempre en la vida, del caso y del contexto.

Hay algún caso, sobre todo en listas de poco ancho y con items pequeños, en los que podríamos convencer al cliente de que se quede así por que no se ve tan mal, y tendríamos todo solucionado con nuestro querido space-between.

Posibles soluciones

En los casos en los que no podemos dejarlo así, la mayoría, nos encontramos con que necesitamos trastocar el funcionamiento básico de space-between. Yo no he encontrado una solución a través de Flexbox, si alguien la conoce que me lo cuente ¡porfaPlis!

De primeras pensé: ya está!, si aplicamos al último item un margin-right:auto hacemos que en lugar de irse al final del contenedor se venga para la izquierda. Si, pero se queda pegado al item anterior, por que, recordemos, no tenemos especificado márgenes para los items, si no que los está aplicando Flexbox automáticamente.

Así que no podemos dejar montado un sistema que vaya bien en todos los casos, teniendo en cuenta, además, que esto luego tiene que flexar e ir adaptándose en los distintos breakpoints, y lo suyo es poder hacerlo con solo cambiar el flex-basis de los items, que es una de las maravillas de Flexbox.

:after que te crió

La primera solución que yo encontré para esto, y que apliqué en algún proyecto, es utilizar un pseudo-elemento :after aplicado a nuestro contenedor.

Como los pseudo-elementos :before y :after se renderizan dentro del elemento al que se aplican, si le ponemos un :after a nuestro contenedor con display: flex, este pseudo-elemento pasará a ser otro flex item más. Así que podemos jugar con él haciendo que ocupe el espacio de los items que no están, para que el resto de items de la fila no se vayan al final:

Si toda la fila está llena el :after simplemente se irá a la siguiente fila, pero, como no tiene ningún contenido, no ocupará espacio alguno.

Sin embargo esto tiene sus limitaciones y es un poco engorroso de manejar. Está bien para un grid de 3 columnas, ya que ahí solo tienes un caso de error: cuando te quedan solo 2 items en una fila y el segundo se va al final. Si queremos aplicarlo a un grid de 4 o más columnas ya se complica la cosa, por que tendríamos que darle ancho suficiente a ese :after como para que ocupe el espacio de 1 o 2 items, si estos no están. Un lío, vaya.

Y todo esto por que el menda se enamoró de space-between y del hecho de no tener que especificar márgenes para los items, si no que los calcule Flexbox él solito aprovechando todo el espacio del contenedor. Y muchos diréis: olvídate!, «pon flex-start y pista!», no? Pues si, al final si.

Adiós space-between

Al final toca resignarse y pasar a hacer el grid con justify-content: flex-start y un margen fijo para cada item.

Adoptando esta solución hay varias salidas distintas, que ya todas implican «truquitos» como los de antaño, lejos del concepto Flexbox, por eso es que jode.

Uno de ellos es ponerle a cada item un margen (o un padding, depende como esté montado el contenido de dicho item) por la derecha, y comenzar a hacer la típica excepción para el último de cada fila.

Siguiendo el mismo ejemplo de querer 4 items por fila, tendríamos que hacer que el cuarto, octavo, etc.. no tengan dicho margen (o padding). O sea, la típica de: nth-child(4n){margin-right:0;} que tan incómoda resulta de manejar de cara a breakpoints y demás, ya que no será todo el rato el cuarto item el que termine la fila : o

Y aquí es cuando entra en juego otra solución un poco más fina, que me sugirió mi amigo Darío BF, que es un crack:

Hacemos una justificación de tipo flex-start y asignamos a cada item un margen (o padding) por ambos lados, le damos al contenedor un ancho un poco superior y lo movemos a la derecha. Es decir, para el mismo ejemplo de 4 items por cada fila: les damos a todos un flex: 0 1 23% y margin: 0 1% 2rem. Con esto nos aseguramos que siempre entren 4 en cada fila y se queden igual de separados entre sí, pero sin embargo no están llegando de principio a fin de su contenedor, si no que también hay margen de 1% al principio y al final.

Pues el truquete del amigo Darío es: dale al padre width: 102%; margin-left: -1%

Y ahí tenemos nuestro grid clavao al ancho total del layout y con los últimos items al principio de la fila. Fastidia, por que no es tan limpio como que Flexbox reparta los márgenes él solito, pero es un resultado muy aceptable.

Editado a posteriori:

Mixin save the Queen!

Unos días después de escribir este post, me encuentro de casualidad con un Codepen de Diana Aceves en el que muestra un mixin de Sass que ella se ha currao para resolver este tema.

Así que lo he probao, me ha costao lo suyo entenderlo (no es que esté yo muy puesto en mixins que digamos), pero finalmente lo he comprendido y lo he conseguido aplicar.

Lo que hace esta maravilla es calcular el espacio que queda entre los items de nuestro grid y aplicáselo a los items que quedan «huérfanos» en la última fila como margin-left, menos al primero. Y, además, ponerle al último item un margin-right: auto para que no se vaya al final del contendor.

¡Es la caña!

Gracias Diana, por salvar a mi querido space-between.

Por mi parte, lo he traducido a Stylus y he puesto que aplique a los items un flex-basis en lugar de un with, por que soy adicto al flex-basis, no por otra cosa.

Aquí está en Codepen para quien guste de usarlo:

See the Pen Stylus mixin to fix space-between (original by @diana_aceves) by roberto tuñón (@rtunon) on CodePen.