Saltar al contenido
flexbox-avanzado.css · devschool

Flexbox a fondo: grow, shrink, basis, wrap y patrones

Lección 19 de 50 · 13 min de lectura · Actualizado el

En esta lección
  1. Cómo reparte el espacio Flexbox
  2. flex-basis: el punto de partida
  3. flex-grow: repartir lo que sobra
  4. flex-shrink: quitar lo que falta
  5. flex: 1 frente a flex: auto
  6. min-width: 0: el problema del texto largo
  7. flex-wrap, align-content y las líneas
  8. gap, row-gap y column-gap
  9. order y el orden de lectura
  10. margin: auto: empujar y separar
  11. Patrón 1: barra de navegación
  12. Patrón 2: tarjetas que se reparten el ancho
  13. Patrón 3: pie pegado y zona con scroll propio
  14. Cuándo no usar Flexbox
  15. Errores frecuentes
  16. Resumen

Con la lección de Flexbox ya sabes colocar elementos en fila o en columna y alinearlos. Lo que muchas veces se queda sin entender es cómo decide Flexbox el ancho de cada hijo: por qué dos columnas con flex: 1 salen iguales y con flex: auto no, por qué un texto largo rompe un diseño o por qué las tarjetas de la última fila quedan más anchas. En esta lección verás el reparto del espacio con números reales, los problemas clásicos y varios patrones de uso diario.

Si todavía no has visto los ejes, justify-content o align-items, empieza por la lección básica.

Cómo reparte el espacio Flexbox

Para cada línea, el navegador sigue siempre estos pasos:

  1. Calcula el tamaño base de cada hijo (flex-basis; si es auto, mira width o el contenido).
  2. Suma esos tamaños base y los compara con el ancho del contenedor (descontando los gap).
  3. Si sobra espacio, lo reparte según flex-grow.
  4. Si falta espacio, lo quita según flex-shrink.
  5. Respeta los límites min-width y max-width de cada hijo.

Con eso se explica casi todo. Veamos cada paso con cifras.

flex-basis: el punto de partida

flex-basis es el tamaño del hijo antes de repartir. Funciona como un width (en una fila) o un height (en una columna), y si ambos existen, gana flex-basis.

.item { width: 300px; flex-basis: 150px; }   /* arranca en 150px, no en 300px */

Valores útiles:

ValorSignificado
autoUsa width, y si no hay, el tamaño del contenido
0Parte de cero: todo el espacio es “sobrante”
200px, 30%Un tamaño concreto
contentEl tamaño del contenido, ignorando width

Consejo: flex-basis es un tamaño deseado, no una garantía. Si hay poco sitio, el hijo encoge, y si hay mucho, puede crecer. Para fijar de verdad un ancho, usa flex: none (o flex: 0 0 200px) junto a él.

flex-grow: repartir lo que sobra

Un contenedor de 700px con tres hijos de flex-basis: 100px suma 300px, así que sobran 400px. Con flex-grow: 1, 2 y 1, el espacio sobrante se divide en 4 partes (1 + 2 + 1) de 100px:

.a { flex: 1 1 100px; }   /* 100 + 1 parte  = 200px */
.b { flex: 2 1 100px; }   /* 100 + 2 partes = 300px */
.c { flex: 1 1 100px; }   /* 100 + 1 parte  = 200px */

Fíjate: flex-grow reparte el sobrante, no el total. El hijo b no mide el doble que los otros (300 frente a 200), porque todos partían de 100px. Para que las proporciones sean exactas, la base debe ser 0.

.a { flex: 1 1 0; }   /* 700 / 4 * 1 = 175px */
.b { flex: 2 1 0; }   /* 700 / 4 * 2 = 350px */
.c { flex: 1 1 0; }   /* 175px */

Esa es la razón por la que flex: 1 se escribe como 1 1 0%: base cero y proporciones limpias.

Y si la suma de los flex-grow es menor que 1, no se reparte todo el sobrante, sino solo esa fracción. Con un único hijo con flex-grow: 0.5 y 400px libres, crece 200px.

flex-shrink: quitar lo que falta

Ahora al revés: un contenedor de 500px con tres hijos de flex-basis 300, 200 y 200px. Suman 700px y faltan 200px. Con flex-shrink: 1 en todos, cabría pensar que cada uno pierde 66px, pero no es así: la reducción se reparte en proporción al flex-shrink multiplicado por el tamaño base. Así, los hijos grandes encogen más.

pesos:  300 x 1 = 300,  200 x 1 = 200,  200 x 1 = 200   (total 700)
pierde: 200 * 300/700 = 85,7px   |   200 * 200/700 = 57,1px   |   57,1px
queda:  214,3px                   |   142,9px                  |   142,9px      (suma 500)

Con flex-shrink: 0 un hijo se niega a encoger (es lo que quieres para un logo o un botón). Un valor mayor que 1 lo hace encoger más rápido que los demás.

Nota: si todos los hijos son rígidos (flex-shrink: 0) y no caben, se desbordan del contenedor. Es una señal de que necesitas flex-wrap o reducir tamaños.

flex: 1 frente a flex: auto

La diferencia entre estos dos atajos se ve con números. Un contenedor de 600px con tres hijos cuyo contenido mide 100, 200 y 100px:

.fila > * { flex: 1; }     /* 1 1 0%  -> base 0, sobran 600 -> 200px cada uno */
.fila > * { flex: auto; }  /* 1 1 auto -> base 100, 200, 100; sobran 200 */
                           /*   cada uno recibe +66,7px: 166,7 / 266,7 / 166,7px */
AtajoEquivale aResultado
flex: 11 1 0%Columnas idénticas, sin importar el contenido
flex: auto1 1 autoCada hijo parte de su contenido: el largo queda más ancho
flex: none0 0 autoRígido, con el tamaño de su contenido
flex: 0 0 200pxAncho fijo de 200px
flex: 1 1 200pxQuiere 200px, pero se adapta

Usa flex: 1 cuando quieras columnas iguales; flex: auto cuando el contenido deba influir en el reparto, por ejemplo en una barra de pestañas.

min-width: 0: el problema del texto largo

Aquí está el error que más tiempo hace perder. Un hijo flex tiene por defecto min-width: auto, que significa: “no encojas por debajo del tamaño de tu contenido”. Con texto sin espacios (una URL, un nombre de archivo largo), el hijo se niega a encoger y empuja a los demás o se sale del contenedor.

.fichero { display: flex; gap: 8px; }
.fichero .nombre { flex: 1; }            /* el nombre largo empuja el botón fuera */

La solución es permitirle encoger a cero:

.fichero .nombre {
  flex: 1;
  min-width: 0;              /* ahora sí puede encoger */
  overflow: hidden;
  text-overflow: ellipsis;   /* puntos suspensivos */
  white-space: nowrap;
}

Lo mismo ocurre en la dirección vertical con min-height: 0 cuando un hijo de una columna flex debe tener su propio scroll (lo verás en el patrón de aplicación más abajo). También overflow: hidden (o auto) en el hijo cambia el min-width automático a 0, y es otra forma de resolverlo.

flex-wrap, align-content y las líneas

Con flex-wrap: wrap, los hijos que no caben bajan a otra línea. El flex-basis es entonces lo que decide cuántos caben por línea; el flex-grow decide cómo se reparte el sobrante de cada línea por separado.

.tarjetas {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}
.tarjeta { flex: 1 1 220px; }

Con 600px de ancho caben dos tarjetas por línea; con 1000px, cuatro. Esto da un diseño adaptable sin media queries.

La última línea ensanchada

Si tienes 5 tarjetas y caben 3 por línea, la segunda línea tiene 2 y, con flex-grow: 1, se estiran para ocupar todo el ancho: quedan más anchas que las de arriba. Tienes dos salidas:

/* 1. Tope de ancho: no crecen más de la cuenta */
.tarjeta { flex: 1 1 220px; max-width: 320px; }

/* 2. Pasa a Grid, que sí mantiene las columnas alineadas */
.tarjetas { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }

Si necesitas columnas exactas en Flexbox, descuenta los gap del cálculo. Para tres por fila con un gap de 16px:

.tarjeta { flex: 0 0 calc((100% - 2 * 16px) / 3); }

align-content: el espacio entre líneas

Cuando hay varias líneas y el contenedor tiene más alto que ellas, align-content reparte ese alto entre las líneas (no entre los hijos). Por defecto vale stretch, que estira las líneas y deja huecos si los hijos no se estiran.

.etiquetas {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  height: 200px;
  align-content: flex-start;   /* las líneas se agrupan arriba, sin huecos */
}

Con una sola línea no hace nada: para eso está align-items.

gap, row-gap y column-gap

gap pone espacio entre los hijos y entre las líneas. Puedes separar los dos ejes:

.tarjetas { gap: 24px 12px; }          /* 24px entre filas, 12px entre columnas */
.tarjetas { row-gap: 24px; column-gap: 12px; }  /* lo mismo, con nombre */

Los gap cuentan en el cálculo del paso 2, así que reducen el espacio disponible. Si las columnas “no caben”, comprueba si el gap es el culpable.

order y el orden de lectura

order mueve un hijo en el orden visual (0 por defecto; menor, antes). Es cómodo para subir una imagen en el móvil:

.articulo { display: flex; flex-direction: column; }
.articulo .foto { order: -1; }   /* la foto pasa arriba */

Pero el teclado y los lectores de pantalla siguen el orden del HTML, así que no lo uses para corregir un HTML mal ordenado. Si el orden visual y el del código se separan mucho, la navegación con Tab salta de forma extraña.

margin: auto: empujar y separar

Un margen auto en un hijo flex se come todo el espacio libre en ese lado, antes de que actúe justify-content. Eso permite separar grupos sin contenedores extra:

.barra { display: flex; align-items: center; gap: 12px; }
.barra .buscar { margin-left: auto; }   /* este y los siguientes, a la derecha */

Con margin-inline: auto en un hijo, queda centrado y los demás a los lados. En una columna, margin-top: auto lo manda abajo del todo.

Patrón 1: barra de navegación

Logo a un lado, enlaces a otro, acciones al final, y que se adapte al móvil:

<header class="barra">
  <a class="logo" href="/">Gimnasio Forma</a>
  <nav class="enlaces" aria-label="Principal">
    <a href="/clases">Clases</a>
    <a href="/tarifas">Tarifas</a>
  </nav>
  <a class="boton" href="/alta">Hazte socio</a>
</header>
.barra {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 16px;
  padding: 12px 16px;
}
.logo { flex: none; font-weight: bold; }
.enlaces { display: flex; gap: 16px; margin-inline: auto 0; } /* empuja a la derecha */
.boton { flex: none; }

El logo y el botón no encogen (flex: none); si no hay sitio, los enlaces bajan a otra línea gracias a flex-wrap.

Patrón 2: tarjetas que se reparten el ancho

.planes { display: flex; flex-wrap: wrap; gap: 16px; }
.plan {
  flex: 1 1 240px;
  max-width: 360px;
  display: flex;
  flex-direction: column;     /* la tarjeta es, a su vez, una columna flex */
}
.plan .precio { margin-top: auto; }   /* el precio siempre abajo */

Aquí hay dos niveles de Flexbox: el contenedor reparte el ancho y cada tarjeta reparte su alto. Es muy habitual anidar flex, y no hay problema.

Patrón 3: pie pegado y zona con scroll propio

El pie que no flota ya lo viste en la lección básica. Una versión más completa es la de aplicación: cabecera y pie fijos y solo la zona central con scroll.

body {
  display: flex;
  flex-direction: column;
  height: 100dvh;          /* alto exacto de la ventana */
  margin: 0;
}
header, footer { flex: none; }
main {
  flex: 1;
  min-height: 0;           /* imprescindible para que el scroll funcione */
  overflow: auto;
}

Sin min-height: 0, main se negaría a encoger por debajo de su contenido, el body crecería más que la ventana y no habría scroll interno. Es exactamente el min-width: 0 de antes, pero en vertical.

Cuándo no usar Flexbox

Si necesitas que las filas y las columnas se alineen entre sí (una tabla de tarjetas, un calendario), Grid es mejor. Para centrar una sola cosa tienes Centrar elementos, y para recetas completas, Maquetaciones típicas.

Errores frecuentes

  • Creer que flex-grow reparte el total. Reparte el sobrante después de las bases. Para proporciones exactas, pon base 0 (flex: 2 1 0).
  • Olvidar min-width: 0. Un texto largo o una imagen ancha no encoge y rompe el diseño.
  • Usar width en el hijo y esperar que mande. Si hay flex-basis, gana este. Y flex-shrink puede encoger el width igualmente.
  • Esperar columnas iguales con flex: auto. Con auto cada una parte de su contenido; para iguales, flex: 1.
  • Tarjetas de la última fila demasiado anchas. Pon max-width o usa Grid con auto-fill.
  • Usar align-content con una línea. No hace nada: lo que buscas es align-items.
  • Olvidar el gap en el cálculo de anchos porcentuales. Tres columnas del 33 % con gap ya no caben: descuenta el gap o usa flex: 1.
  • Depender de order para el orden de lectura. El teclado sigue el HTML.

Resumen

Propiedad o trucoQué hace
flex-basisTamaño de partida antes de repartir
flex-growReparte el espacio sobrante en proporción
flex-shrinkReparte lo que falta, ponderado por el tamaño base
flex: 11 1 0%: columnas iguales
flex: auto1 1 auto: parte del contenido
flex: none0 0 auto: rígido
min-width: 0Deja que un hijo encoja por debajo de su contenido
flex-wrap + flex: 1 1 220pxTarjetas adaptables sin media queries
align-contentReparte el espacio entre líneas (con wrap)
gap, row-gap, column-gapSeparación entre hijos y líneas
margin: autoEmpuja un hijo al extremo
min-height: 0Scroll interno en una columna flex

En la siguiente lección aprenderás Grid, la herramienta que complementa a Flexbox cuando necesitas filas y columnas a la vez; más adelante tendrás Grid a fondo.

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

flexbox-avanzado.html
Resultado

Pon a prueba lo que has aprendido

[CSS] Un contenedor flex mide 700px y tiene tres hijos con flex: 1 1 100px, flex: 2 1 100px y flex: 1 1 100px. ¿Cuánto mide el segundo?

[CSS] Un contenedor de 600px tiene tres hijos cuyo contenido mide 100, 200 y 100px. ¿Qué diferencia hay entre flex: 1 y flex: auto?

[CSS] Un nombre de archivo muy largo empuja el botón fuera del contenedor flex. ¿Qué añadirías al hijo con el texto?
.nombre { flex: 1; }
.fila { display: flex; }

[CSS] En una columna flex a 100dvh, el main tiene flex: 1 y overflow: auto, pero no hay scroll interno y la página se alarga. ¿Qué falta?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.