Todo lo que viene usa solo lo que ya conocemos:
display: flex, flex-direction,
gap, justify-content,
flex y flex-wrap. No hace falta nada nuevo.
Una regla que vale para todo lo que sigue:
flex-wrap: wrap se pone siempre en el contenedor que reparte en
horizontal, porque el ancho es lo único que se acaba: hacia abajo siempre hay sitio,
que para eso está el scroll. En un contenedor en columna, el wrap no hace
nada salvo que se le dé una altura.
Es el caso normal: cada opción ocupa lo que mide su palabra. Para eso, simplemente no se pone nada en los hijos.
<nav class="menu">
<ul>
<li><a href="#">Inicio</a></li>
<li><a href="#">Work</a></li>
<li><a href="#">About me</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
.menu ul { display: flex; flex-wrap: wrap; gap: 4px; list-style: none; margin: 0; padding: 4px; background: #2a2a55; } .menu a { display: block; padding: 10px 18px; background: #4a4a85; color: #fff; text-decoration: none; font-family: Verdana, sans-serif; } .menu a:hover { background: #6a6aa5; }
Compáralo con lo que pasa al añadir flex: 1 a cada opción: todas
pasan a medir lo mismo y «Work» queda con un hueco enorme a los lados.
<nav class="menu">
<ul>
<li><a href="#">Inicio</a></li>
<li><a href="#">Work</a></li>
<li><a href="#">About me</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
.menu ul { display: flex; flex-wrap: wrap; gap: 4px; list-style: none; margin: 0; padding: 4px; background: #2a2a55; } .menu a { display: block; padding: 10px 18px; background: #4a4a85; color: #fff; text-decoration: none; font-family: Verdana, sans-serif; } .menu a:hover { background: #6a6aa5; } /* lo único que se añade: todas las opciones miden lo mismo */ .menu li { flex: 1; text-align: center; }
La idea: en un menú, dejar que cada opción mida su contenido es la opción por defecto y la correcta. Repartir en partes iguales solo tiene sentido en una barra de botones de anchura fija.
El mismo HTML. Lo único que cambia es la dirección:
<nav class="menu">
<ul>
<li><a href="#">Inicio</a></li>
<li><a href="#">Work</a></li>
<li><a href="#">About me</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
.menu ul { display: flex; flex-direction: column; gap: 4px; width: 200px; list-style: none; margin: 0; padding: 4px; background: #2a2a55; } .menu a { display: block; padding: 10px 18px; background: #4a4a85; color: #fff; text-decoration: none; font-family: Verdana, sans-serif; } .menu a:hover { background: #6a6aa5; }
Es el caso más repetido en toda la web, y se resuelve con una sola propiedad:
<header class="cabecera"> <span class="logo">MiEstudio</span> <nav> <ul> <li><a href="#">Work</a></li> <li><a href="#">About me</a></li> <li><a href="#">Contact</a></li> </ul> </nav> </header>
.cabecera { display: flex; flex-wrap: wrap; justify-content: space-between; /* uno a cada extremo */ align-items: center; /* centrados en vertical */ gap: 10px; background: #2a2a55; padding: 8px 14px; font-family: Verdana, sans-serif; } .logo { color: #fff; font-weight: bold; font-size: 1.2rem; } /* el segundo flexbox, dentro del primero */ .cabecera ul { display: flex; gap: 14px; list-style: none; margin: 0; padding: 0; } .cabecera a { color: #fff; text-decoration: none; } .cabecera a:hover { text-decoration: underline; }
Hay dos flexbox anidados: la cabecera reparte el logo y el menú a los extremos, y dentro el menú coloca sus opciones en fila.
Una tira corta con separación fija. Lo único particular es que no crecen: cada icono mide lo suyo.
<div class="redes"> <a href="#" class="icono">IG</a> <a href="#" class="icono">FB</a> <a href="#" class="icono">IN</a> <a href="#" class="icono">YT</a> </div>
.redes { display: flex; gap: 8px; } .icono { display: flex; align-items: center; justify-content: center; width: 38px; height: 38px; border-radius: 50%; background: #4a4a85; color: #fff; text-decoration: none; font-family: Verdana, sans-serif; font-size: .8rem; } .icono:hover { background: #2a2a55; }
Está explicado aparte, en su propio apartado. Recuerda que combina
display: none, :hover y
position: absolute.
Para una fila de fotos, como la portada de una web de moda. Con
wrap, cuando la pantalla se estrecha las fotos van cayendo a la
línea siguiente en vez de aplastarse.
<div class="galeria">
<img src="IMG/naturaleza-lago-bn.jpg"
alt="Lago entre montañas en blanco y negro">
<img src="IMG/naturaleza-arroyo.jpg"
alt="Arroyo de montaña en un bosque de Trentino">
<img src="IMG/naturaleza-abetos.jpg"
alt="Arroyo entre abetos bajo un cielo nublado">
<img src="IMG/naturaleza-valle.jpg"
alt="Valle con niebla al amanecer">
<img src="IMG/retrato-contraluz.jpg"
alt="Retrato de mujer a contraluz">
<img src="IMG/retrato-rizado.jpg"
alt="Primer plano de mujer con pelo rizado">
<img src="IMG/bn-hombre.jpg"
alt="Retrato en blanco y negro de hombre pensativo">
</div>
.galeria { display: flex; flex-wrap: wrap; gap: 8px; } .galeria img { flex: 1 1 12rem; /* al menos 12rem; el sobrante, repartido */ aspect-ratio: 4 / 3; /* la forma la decido yo, no el tamaño */ object-fit: cover; /* y la foto la llena sin deformarse */ min-width: 0; } /* Estrecha la ventana: las fotos van cayendo solas a la línea siguiente. */
Estrecha la ventana del navegador y observa cómo se reorganizan solas, sin escribir una sola medida de pantalla.
Una sola tira no da una galería cuadrada: por mucho que envuelva, sigue siendo una fila detrás de otra. Para una galería cuadrada hay que meter tiras dentro de tiras.
Y hay dos maneras de montarlas, según cuál sea la tira de fuera. En las demostraciones que siguen, el borde azul marca cada una de las tiras de dentro.
Una galería cuadrada necesita que todas las fotos ocupen el mismo hueco. Pero las fotos no vienen iguales: unas son verticales, otras apaisadas, otras panorámicas. Si se les fija el ancho y el alto sin más, el navegador las estira o las aplasta para que quepan, y una cara sale con la nariz ancha.
object-fit: cover es lo que lo evita. La foto se amplía
hasta llenar el hueco conservando su proporción, y lo que se
sale por los bordes se recorta. Nunca se deforma: se pierde un trozo.
img {
width: 100%;
height: 120px; /* el hueco lo decido yo */
object-fit: cover; /* la foto lo llena sin deformarse */
}
Tiene un hermano, object-fit: contain, que hace lo
contrario: mete la foto entera dentro del hueco sin recortar nada, y deja franjas
vacías a los lados. Para un grid se usa cover;
contain sirve cuando no se puede perder ni un trozo de la imagen,
como en un plano o un logotipo.
De aquí en adelante: donde las fotos salgan todas del mismo
tamaño es porque llevan cover y están recortadas. Donde cada una
conserve su forma es porque no lo llevan.
Si todas las tiras son iguales, tenemos un grid:
<div class="galeria"> <div class="column"> <img src="IMG/naturaleza-arroyo.jpg" alt="Arroyo de montaña en un bosque de Trentino"> <img src="IMG/naturaleza-abetos.jpg" alt="Arroyo entre abetos bajo un cielo nublado"> <img src="IMG/naturaleza-valle.jpg" alt="Valle con niebla al amanecer"> </div> <div class="column"> <img src="IMG/retrato-contraluz.jpg" alt="Retrato de mujer a contraluz"> <img src="IMG/retrato-rizado.jpg" alt="Primer plano de mujer con pelo rizado"> <img src="IMG/retrato-panuelo.jpg" alt="Retrato de hombre con sombrero y pañuelo rojo"> </div> <div class="column"> <img src="IMG/bn-hombre.jpg" alt="Retrato en blanco y negro de hombre pensativo"> <img src="IMG/bn-cupula.jpg" alt="Cúpula del Palacio de Bellas Artes en blanco y negro"> <img src="IMG/bn-caballo.jpg" alt="Caballo en blanco y negro"> </div> </div>
.galeria { display: flex; flex-wrap: wrap; gap: 10px; } /* las tiras de dentro, verticales: NO llevan wrap */ .column { flex: 1; min-width: 10rem; display: flex; flex-direction: column; gap: 8px; border: 2px solid #2a5db0; /* solo para ver dónde empieza y acaba cada tira */ padding: 6px; } .column img { width: 100%; /* el ancho, el de su columna */ aspect-ratio: 4 / 3; /* la forma; el alto sale del ancho */ object-fit: cover; /* la foto la llena sin deformarse */ }
Recuerda que solo la tira horizontal lleva wrap.
Las verticales crecen hacia abajo, que es lo que se quiere. Al estrechar la pantalla,
las columnas enteras caen una debajo de otra.
Si las fotos tienen distinto tamaño se consigue un efecto interesante: todas mantienen constante el ancho, que es el de su columna, y el alto se alarga o se estrecha según su relación de aspecto.
<div class="galeria"> <div class="column"> <img src="IMG/naturaleza-arroyo.jpg" alt="Arroyo de montaña en un bosque de Trentino"> <img src="IMG/naturaleza-abetos.jpg" alt="Arroyo entre abetos bajo un cielo nublado"> <img src="IMG/naturaleza-valle.jpg" alt="Valle con niebla al amanecer"> </div> <div class="column"> <img src="IMG/retrato-contraluz.jpg" alt="Retrato de mujer a contraluz"> <img src="IMG/retrato-rizado.jpg" alt="Primer plano de mujer con pelo rizado"> <img src="IMG/retrato-panuelo.jpg" alt="Retrato de hombre con sombrero y pañuelo rojo"> </div> <div class="column"> <img src="IMG/bn-hombre.jpg" alt="Retrato en blanco y negro de hombre pensativo"> <img src="IMG/bn-cupula.jpg" alt="Cúpula del Palacio de Bellas Artes en blanco y negro"> <img src="IMG/bn-caballo.jpg" alt="Caballo en blanco y negro"> </div> </div>
.galeria { display: flex; flex-wrap: wrap; gap: 10px; } /* las tiras de dentro, verticales: NO llevan wrap */ .column { flex: 1; min-width: 10rem; display: flex; flex-direction: column; gap: 8px; border: 2px solid #2a5db0; /* solo para ver dónde empieza y acaba cada tira */ padding: 6px; } .column img { width: 100%; height: auto; /* el alto lo pone la foto, según su proporción */ }
Aquí el wrap va en las tiras de dentro, que son
las horizontales. La de fuera apila hacia abajo, que es lo que se quiere.
Con todas las tiras iguales, el mismo grid:
<div class="galeria"> <div class="row"> <img src="IMG/naturaleza-arroyo.jpg" alt="Arroyo de montaña en un bosque de Trentino"> <img src="IMG/naturaleza-abetos.jpg" alt="Arroyo entre abetos bajo un cielo nublado"> <img src="IMG/naturaleza-valle.jpg" alt="Valle con niebla al amanecer"> </div> <div class="row"> <img src="IMG/retrato-contraluz.jpg" alt="Retrato de mujer a contraluz"> <img src="IMG/retrato-rizado.jpg" alt="Primer plano de mujer con pelo rizado"> <img src="IMG/retrato-panuelo.jpg" alt="Retrato de hombre con sombrero y pañuelo rojo"> </div> <div class="row"> <img src="IMG/bn-hombre.jpg" alt="Retrato en blanco y negro de hombre pensativo"> <img src="IMG/bn-cupula.jpg" alt="Cúpula del Palacio de Bellas Artes en blanco y negro"> <img src="IMG/bn-caballo.jpg" alt="Caballo en blanco y negro"> </div> </div>
.galeria { display: flex; flex-direction: column; gap: 10px; } /* las tiras de dentro, horizontales: AQUÍ va el wrap */ .row { display: flex; flex-wrap: wrap; gap: 8px; border: 2px solid #2a5db0; /* solo para ver dónde empieza y acaba cada tira */ padding: 6px; } .row img { flex: 1; /* todas la misma parte del ancho */ min-width: 0; aspect-ratio: 4 / 3; /* la forma; el alto sale del ancho */ object-fit: cover; /* la foto la llena sin deformarse */ }
Y con fotos de distinto tamaño, el efecto es el simétrico del anterior: ahora lo que se mantiene constante es el alto, que es el de su fila, y lo que varía es el ancho.
<div class="galeria"> <div class="row"> <img src="IMG/naturaleza-arroyo.jpg" alt="Arroyo de montaña en un bosque de Trentino" style="flex: 75"> <img src="IMG/naturaleza-abetos.jpg" alt="Arroyo entre abetos bajo un cielo nublado" style="flex: 150"> <img src="IMG/naturaleza-valle.jpg" alt="Valle con niebla al amanecer" style="flex: 177"> </div> <div class="row"> <img src="IMG/retrato-contraluz.jpg" alt="Retrato de mujer a contraluz" style="flex: 69"> <img src="IMG/retrato-rizado.jpg" alt="Primer plano de mujer con pelo rizado" style="flex: 133"> <img src="IMG/retrato-panuelo.jpg" alt="Retrato de hombre con sombrero y pañuelo rojo" style="flex: 77"> </div> <div class="row"> <img src="IMG/bn-hombre.jpg" alt="Retrato en blanco y negro de hombre pensativo" style="flex: 140"> <img src="IMG/bn-cupula.jpg" alt="Cúpula del Palacio de Bellas Artes en blanco y negro" style="flex: 56"> <img src="IMG/bn-caballo.jpg" alt="Caballo en blanco y negro" style="flex: 133"> </div> </div>
.galeria { display: flex; flex-direction: column; gap: 10px; } /* las tiras de dentro, horizontales: AQUÍ va el wrap */ .row { display: flex; flex-wrap: wrap; gap: 8px; border: 2px solid #2a5db0; /* solo para ver dónde empieza y acaba cada tira */ padding: 6px; } .row img { width: 0; /* el ancho no lo escribo: sale del reparto */ min-width: 0; height: auto; /* y el alto, de la proporción de la foto */ } /* Las partes de cada foto son su propia proporción: una panorámica pide más ancho que una vertical. Repartido así, las tres acaban con el mismo alto y la fila se llena entera, mida lo que mida la pantalla. */
En los dos casos ninguna foto se deforma, porque cada una conserva su proporción. La diferencia está en qué lado queda recto: con columnas, los laterales; con filas, la base de cada tira.
Y hay una asimetría entre los dos: una columna puede crecer hacia
abajo cuanto haga falta, pero una fila no puede crecer hacia la derecha, porque la
pantalla se acaba. Por eso, en la galería de filas, los anchos de las fotos se dan
en partes con flex, y no en píxeles: así cada fila
se llena entera sea cual sea la pantalla.
La misma estructura de tiras anidadas sirve para agrupar las fotos por temas, poniéndole a cada columna su propio título.
<div class="galeria"> <div class="column"> <h3>Naturaleza</h3> <img src="IMG/naturaleza-arroyo.jpg" alt="Arroyo de montaña en un bosque de Trentino"> <img src="IMG/naturaleza-abetos.jpg" alt="Arroyo entre abetos bajo un cielo nublado"> <img src="IMG/naturaleza-valle.jpg" alt="Valle con niebla al amanecer"> </div> <div class="column"> <h3>Retratos</h3> <img src="IMG/retrato-contraluz.jpg" alt="Retrato de mujer a contraluz"> <img src="IMG/retrato-rizado.jpg" alt="Primer plano de mujer con pelo rizado"> </div> <div class="column"> <h3>Ciudad</h3> <img src="IMG/ciudad-amanecer.jpg" alt="Rascacielos de Ciudad de México al amanecer"> <img src="IMG/ciudad-aerea.jpg" alt="Vista aérea de una ciudad extensa"> <img src="https://images.pexels.com/photos/12333846/pexels-photo-12333846.jpeg?auto=compress&cs=tinysrgb&w=600" alt="Rascacielos iluminados de noche"> </div> </div>
.galeria { display: flex; flex-wrap: wrap; gap: 10px; } /* las tiras de dentro, verticales: NO llevan wrap */ .column { flex: 1; min-width: 10rem; display: flex; flex-direction: column; gap: 8px; border: 2px solid #2a5db0; /* solo para ver dónde empieza y acaba cada tira */ padding: 6px; } .column img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; } .column h3 { margin: 0; font-family: Verdana, sans-serif; font-size: 1rem; color: #2a2a55; } /* Cada columna lleva las fotos que haga falta: las demás no se enteran. */
Cada columna lleva las fotos que haga falta, sin que las demás se enteren.
Con las tiras anidadas se construyen galerías perfectamente válidas. Pero hay tres cosas que no se pueden hacer:
Para eso está Grid, que veremos en la clase siguiente.
Una tarjeta es una caja en columna: imagen, título, texto y enlace. Y varias tarjetas juntas son una tira horizontal.
El detalle que resuelve Flexbox, y que a mano no sale, es este: los textos tienen distinta longitud, pero los botones deben quedar alineados.
<div class="cards"> <article class="card"> <img src="IMG/naturaleza-valle.jpg" alt="Valle con niebla al amanecer"> <h3>Naturaleza</h3> <p>Un texto corto.</p> <a href="#" class="boton">Ver más</a> </article> <article class="card"> <img src="IMG/retrato-rizado.jpg" alt="Primer plano de mujer con pelo rizado"> <h3>Retratos</h3> <p>Un texto bastante más largo que el de la tarjeta de al lado, para que se vea que los botones siguen alineados aunque el contenido no mida lo mismo.</p> <a href="#" class="boton">Ver más</a> </article> <article class="card"> <img src="https://images.pexels.com/photos/12333846/pexels-photo-12333846.jpeg?auto=compress&cs=tinysrgb&w=600" alt="Rascacielos iluminados de noche"> <h3>Ciudad</h3> <p>Otro texto, de longitud media, algo más largo que el primero.</p> <a href="#" class="boton">Ver más</a> </article> </div>
.cards { display: flex; flex-wrap: wrap; gap: 14px; font-family: Verdana, sans-serif; } .card { flex: 1; min-width: 12rem; display: flex; flex-direction: column; border: 1px solid #8686a8; background: #f2f2f8; } .card img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; } .card h3 { margin: .7rem .8rem .4rem; font-size: 1rem; } .card p { margin: 0 .8rem .8rem; font-size: .9rem; } /* LA CLAVE: todo el espacio que sobre, por encima del botón */ .boton { margin-top: auto; display: block; text-align: center; padding: 8px; background: #2a2a55; color: #fff; text-decoration: none; } .boton:hover { background: #4a4a85; }
margin-top: auto significa «todo el espacio que sobre, ponlo encima de
mí». El resultado es que el enlace se va al fondo de su tarjeta, y los tres quedan a la
misma altura.
Las fotos son de Pexels, con licencia libre: se pueden usar y modificar sin pagar y sin citar la fuente. Citarla no es obligatorio, pero es lo correcto, y aquí sirve además de ejemplo de cómo se hace. Lo que la licencia no permite es revenderlas sin editar, redistribuirlas en otro banco de imágenes ni usar a las personas que aparecen de forma que queden en mal lugar.
naturaleza-lago-bn.jpg — Lago entre montañas en blanco y negro. Foto de Aysegul Aytoren, ficha en Pexels.naturaleza-arroyo.jpg — Arroyo de montaña en un bosque de Trentino. Foto de Pluto91 Operatic, ficha en Pexels.naturaleza-abetos.jpg — Arroyo entre abetos bajo un cielo nublado. Foto de Snapwire, ficha en Pexels.naturaleza-valle.jpg — Valle con niebla al amanecer. Foto de Johannes Plenio, ficha en Pexels.retrato-contraluz.jpg — Retrato de mujer a contraluz. Foto de Asep Awaludin, ficha en Pexels.retrato-rizado.jpg — Primer plano de mujer con pelo rizado. Foto de Eric Quinones, ficha en Pexels.retrato-panuelo.jpg — Retrato de hombre con sombrero y pañuelo rojo. Foto de Aa Dil, ficha en Pexels.bn-hombre.jpg — Retrato en blanco y negro de hombre pensativo. Foto de Samuel Moses, ficha en Pexels.bn-cupula.jpg — Cúpula del Palacio de Bellas Artes en blanco y negro. Foto de ἐμμανυελ, ficha en Pexels.bn-caballo.jpg — Caballo en blanco y negro. Foto de Ruhul Amin Akash, ficha en Pexels.ciudad-amanecer.jpg — Rascacielos de Ciudad de México al amanecer. Foto de Yuriko Salazar Luna, ficha en Pexels.ciudad-aerea.jpg — Vista aérea de una ciudad extensa. Foto de Victor Armas, ficha en Pexels.ciudad-noche.jpg — Rascacielos iluminados de noche. Foto de Pxleta, ficha en Pexels.