Flexbox: fila y columna
En resumen: Flexbox coloca los elementos en una fila o en una columna a lo largo del eje principal, los alinea en el eje transversal y reparte el espacio libre con flex-grow.
Problema
La cabecera en tres líneas
La nueva cabecera de la tienda debería ser una sola línea: el logo a la izquierda, el menú a su lado y el carrito a la derecha. En una computadora son tres líneas, una debajo de otra.
Consola
El CSS no tiene ningún error. <nav> es un elemento de bloque, y los elementos de bloque se apilan, cada uno en una línea entera. Los enlaces son elementos en línea y comparten línea con el texto que los rodea. Así que el logo está en la primera línea, el menú en la segunda y el carrito en la tercera.
La idea
Contenedor y elementos
Los hijos de la cabecera necesitan otra forma de colocarse. Es display: flex en el padre:
header { display: flex;}- El elemento con
display: flexes un contenedor flex. Sus hijos (solo los directos) son elementos flex. - Los elementos se colocan uno al lado del otro a lo largo del eje principal. Por defecto va de izquierda a derecha.
flex-direction: columnlo gira de arriba abajo. - En ángulo recto con él está el eje transversal.
La disposición se define en el contenedor, no en los hijos:
justify-contentreparte a lo largo del eje principal:flex-start(al principio),center,flex-end,space-between(el primero al principio, el último al final, el espacio entre ellos).align-itemsalinea a lo largo del eje transversal:stretch(el valor por defecto, todos tan altos como la línea),center,flex-start,flex-end.gappone el mismo espacio entre los elementos, y nada antes del primero ni después del último.
Un elemento puede ser a la vez elemento flex y contenedor flex. nav { display: flex; gap: 12px; } coloca los enlaces del menú, y el propio menú es un elemento de la cabecera.
Pruébalo
Logo a la izquierda, carrito a la derecha
Pon la cabecera en una sola línea: el logo en el borde izquierdo, el carrito en el derecho y el menú entre ellos. Usa justify-content. La comprobación mide en cuántas filas están los hijos de <header>.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>La tienda</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<a class="logo" href="/">La tienda</a>
<nav>
<a href="/bread">Pan</a>
<a href="/milk">Leche</a>
<a href="/fruit">Frutas</a>
</nav>
<a class="cart" href="/cart">Carrito (2)</a>
</header>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
header {
padding: 12px 16px;
background: #f3efe6;
display: flex;
justify-content: space-between;
align-items: center;
}
a { color: #1f2430; }
.logo { font-weight: 700; }
nav a { margin-right: 12px; }
display: flex en header pone los tres hijos en fila. justify-content: space-between manda el primero al principio y el último al final, y el espacio libre queda en medio. align-items: center los centra en vertical. Aquí los tres tienen la misma altura, pero si el carrito pasa a ser un botón con padding, la diferencia se nota.
Pruébalo
Encuentra el error
La cabecera está en fila, pero el carrito no se va a la derecha. ¿Por qué?
Elige la línea con el error.
justify-content reparte los hijos de un contenedor, por eso se escribe en el contenedor. En la línea 6 está en .cart: no tiene hijos flex, y la propiedad no hace nada. Debería ser justify-content: space-between en header, o margin-left: auto en .cart, de lo que trata la siguiente tarjeta.
La idea
El espacio libre
La cabecera tiene también un campo de búsqueda. Debe ocupar todo el espacio entre el logo y el carrito, sea cual sea el ancho de la pantalla. Aquí ayudan tres propiedades del elemento flex:
flex-basises la base: el ancho del elemento antes de repartir nada. Por defecto esauto: suwidth, o tan ancho como su contenido.flex-growdice cuántas partes del espacio libre toma el elemento. Por defecto es 0: no crece.flex-shrinkdice cuánto se encoge si no hay espacio suficiente. Por defecto es 1.
El navegador lo calcula así:
- Coloca cada elemento con su base.
- El espacio libre es el ancho del contenedor menos las bases y los gap.
- Reparte el espacio libre según
flex-grow, y cada elemento crece su parte.
La propiedad flex define las tres a la vez. flex: 1 es flex-grow: 1, flex-shrink: 1 y flex-basis: 0%: el elemento crece y su base es cero. Por eso tres elementos con flex: 1 quedan igual de anchos, tengan el texto que tengan.
Míralo
Mira cómo se reparte el espacio
Una barra de 600px con tres elementos. Fíjate en la suma de arriba y en el espacio libre verde.
.bar { display: flex; width: 600px; gap: 20px;}.logo { flex-basis: 120px; }.search { flex-basis: 100px; flex-grow: 2; }.cart { flex-basis: 100px; flex-grow: 1; }Paso a paso, en palabras
- .bar es un contenedor flex de 600px de ancho. Sus hijos son elementos flex: se colocan en fila a lo largo del eje principal, de izquierda a derecha.
- Primero cada elemento recibe su flex-basis: 120, 100 y 100px. Es su ancho antes de repartir nada.
- Entre tres elementos hay dos gap de 20px. Ocupado: 120 + 100 + 100 + 2 × 20 = 360px.
- Lo que sobra es el espacio libre: 600 − 360 = 240px.
- flex-grow dice cuántas partes toma cada elemento: logo 0, search 2, cart 1, tres en total. Una parte son 240 : 3 = 80px.
- Cada elemento crece según sus partes: search 160px, cart 80px y logo se queda igual. Los anchos son 120, 260 y 180px.
- 120 + 20 + 260 + 20 + 180 = 600: la fila está llena. El navegador da exactamente estos anchos. search no es el doble de ancho que cart: solo el espacio libre se reparte 2 : 1.
search tiene flex-grow: 2 y cart tiene 1, pero search no es el doble de ancho: 260 frente a 180. Solo se reparte el espacio libre, y las bases se quedan. Por eso, si quieres columnas iguales, la base tiene que ser cero: flex: 1.
Pruébalo
La búsqueda toma el resto
El logo mide 120px, el carrito 110px y la cabecera llega hasta 640px. Haz que el campo de búsqueda ocupe todo el espacio entre ellos. La comprobación mide su ancho en píxeles: 640 − 2 × 16 (padding) − 120 − 110 − 2 × 16 (gap) = 346px.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>La tienda</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<a class="logo" href="/">La tienda</a>
<input type="search" aria-label="Buscar" placeholder="Pan, leche…">
<a class="cart" href="/cart">Carrito (2)</a>
</header>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
header {
max-width: 640px;
padding: 12px 16px;
display: flex;
align-items: center;
gap: 16px;
background: #f3efe6;
}
a { color: #1f2430; }
.logo { width: 120px; font-weight: 700; }
.cart { width: 110px; text-align: right; }
input {
flex: 1;
padding: 6px 8px;
}
flex: 1 en el campo: es el único elemento con flex-grow, así que toma todo el espacio libre. El logo y el carrito conservan su ancho. width: 100% no funciona: el campo pide toda la cabecera, el espacio no alcanza y flex-shrink encoge también el logo y el carrito.
Pruébalo
Uno a la derecha
A veces quieres el logo y el menú a la izquierda y solo el carrito a la derecha. space-between no sirve: el menú se iría al centro. Hay una propiedad del propio elemento: un margin con el valor auto toma todo el espacio libre de su lado y empuja el elemento.
header { display: flex; gap: 16px; }
.cart { margin-left: ; }margin-left: auto: el margen izquierdo del carrito se come el espacio libre, y el carrito queda en el borde derecho. El logo y el menú siguen juntos a la izquierda. En un contenedor flex los márgenes auto se calculan antes que justify-content.
Pruébalo
Una línea nueva cuando no caben
Por defecto un contenedor flex mantiene todos los elementos en una línea (flex-wrap: nowrap). Si no caben, los encoge, y cuando ya no puede, la línea se sale de la pantalla. Con flex-wrap: wrap los elementos que no caben pasan a una línea nueva.
En un teléfono (360px) los cuatro productos están apretados en una línea y la página se desplaza hacia los lados. Ponlos de dos en dos, cada uno de al menos 150px, y los dos de una línea igual de anchos. La comprobación cuenta las líneas y las columnas y mide el ancho de las tarjetas.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Ofertas</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Ofertas</h1>
<div class="products">
<article class="card">
<h2>Pan blanco</h2>
<p>1,20 €</p>
</article>
<article class="card">
<h2>Leche fresca</h2>
<p>1,60 €</p>
</article>
<article class="card">
<h2>Queso blanco</h2>
<p>7,90 €</p>
</article>
<article class="card">
<h2>Manzanas</h2>
<p>2,10 €</p>
</article>
</div>
</main>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
main { padding: 12px; }
h1 { margin: 0 0 12px; font-size: 1.4rem; }
.products {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.card {
flex: 1 1 150px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
}
.card h2 { margin: 0 0 4px; font-size: 1rem; }
.card p { margin: 0; }
flex-wrap: wrap en el contenedor permite una línea nueva. flex: 1 1 150px en la tarjeta: la base es 150px, así que caben dos (150 + 12 + 150 = 312 ≤ 336) y tres no. Luego el espacio libre de la línea se reparte por igual: cada una mide (336 − 12) : 2 = 162px. flex-wrap: wrap solo hace líneas, pero cada tarjeta mide lo que mide su texto.
La idea
El nombre que no se encoge
En el carrito el nombre del producto está junto a la imagen, y a su lado van la cantidad y el precio. El nombre es largo. Queremos cortarlo con «…» en una línea: white-space: nowrap, overflow: hidden y text-overflow: ellipsis. El bloque con el nombre tiene flex: 1. Sin embargo, en un teléfono la línea se sale de la pantalla y el precio queda apretado:
Consola
La causa es una sola regla. Un elemento flex no se encoge por debajo de su contenido más estrecho: min-width vale auto por defecto, no 0. Para .info el contenido más estrecho es el nombre entero en una línea. min-width: 0 en el elemento flex quita ese límite. Entonces .info se encoge, y el encabezado de dentro corta el texto.
Alguien añadió min-width: 0, pero el nombre sigue sin cortarse. ¿Qué línea está mal?
min-width: 0 de la línea 4 está en el encabezado .name. No es un elemento flex: el elemento flex es .info, su padre. El límite está en .info, así que min-width: 0 tiene que ir allí.
Más precisamente, para curiosos
Encogerse. Si las bases y los gap suman más que el contenedor, el espacio libre es negativo y los elementos se encogen. El reparto no depende solo de flex-shrink, sino de flex-shrink × la base: un elemento ancho se encoge más. La barra de la escena, pero de 280px: 120 + 100 + 100 + 40 = 360, faltan 80px. Los pesos son 120, 100 y 100. logo pierde 80 × 120 : 320 = 30px y los otros 25px cada uno: 90, 75 y 75px. Un elemento con flex-shrink: 0 no se encoge.
Los valores de flex. flex: 1 es 1 1 0%. flex: auto es 1 1 auto: crece, pero desde el ancho de su contenido. flex: none es 0 0 auto: ni crece ni se encoge. flex: 0 0 64px es un elemento de exactamente 64px.
Lo que no se encoge. Las imágenes y los campos tienen un ancho propio, y min-width: auto les impide encogerse. Por eso en el carrito el precio (<strong>) queda apretado y la imagen no.
Otro camino. overflow: hidden en el propio elemento flex también quita el límite: un elemento que recorta su contenido puede encogerse hasta 0.
Un elemento distinto. align-self alinea un solo elemento en el eje transversal. order cambia su lugar en la pantalla pero no en el árbol: el teclado y el lector de pantalla siguen el árbol. Por eso no movemos con order lo que la gente pulsa.
Líneas con wrap. align-content coloca las propias líneas en el eje transversal cuando el contenedor es más alto que ellas. display: inline-flex crea un contenedor que va en el texto como una palabra.
Ejercicio
Una línea del carrito
Coloca una línea del carrito en un teléfono (360px): la imagen, el nombre, la cantidad y el precio en fila, centrados en vertical, con 12px entre ellos. La imagen sigue en 64px, la cantidad en 56px y el precio en 72px. El nombre toma el resto del espacio (aquí 108px) y va en una línea, y si es largo, se corta con «…». La página no se desplaza hacia los lados.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Carrito</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Carrito</h1>
<div class="line">
<img class="thumb" src="img/bread.svg" alt="">
<div class="info">
<h2 class="name">Pan integral de centeno con semillas, en rebanadas, 500 g</h2>
<p class="unit">1,90 € / ud.</p>
</div>
<input class="qty" type="number" value="2" min="1" aria-label="Cantidad">
<strong class="price">3,80 €</strong>
</div>
</main>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
h1 { margin: 0; padding: 12px; font-size: 1.4rem; }
.line {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-top: 1px solid #ddd;
}
.thumb { width: 64px; height: 64px; border-radius: 8px; }
.info {
flex: 1;
min-width: 0;
}
.name {
margin: 0 0 4px;
font-size: 1rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.unit { margin: 0; color: #6b7180; }
.qty { width: 56px; }
.price { width: 72px; text-align: right; }
.line { display: flex; align-items: center; gap: 12px;}.info { flex: 1; min-width: 0; }.name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}flex: 1 da a .info una base de 0 y todo el espacio libre: 336 − 64 − 56 − 72 − 36 = 108px. Con flex-grow: 1 la base sigue siendo la longitud del nombre. Entonces el espacio es negativo y flex-shrink aprieta el precio. Sin min-width: 0 el nombre no se encoge y la línea se sale de la pantalla.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: El documento HTML · Formularios · Selectores y la cascada · El modelo de caja
- Continúa en: Grid: filas y columnas · Diseño responsive · El escaparate de la tienda
- Ejercicios sobre el tema: CSS: el aspecto de la página