Clase 32 · Layout y texto avanzados
Las piezas que faltan para dominar Flex y Grid, el control del texto largo (puntos suspensivos, guiones, titulares equilibrados), las unidades y funciones nuevas (dvh, hsl(), color-mix()) y subgrid. Cada bloque termina con su demo.
Completando Flex y Grid
Repasamos lo que vimos en las clases 7, 8 y 28 y añadimos las propiedades que más se usan y aún no conocemos.
- Flex:
align-self,order,flex-flow. - Atajos de alineación:
place-items,place-content,place-self. - Grid:
grid-template-areas,grid-auto-rows,grid-auto-flow.
align-self, order y flex-flow
Propiedades de Flex que se aplican a los hijos.
align-self sobrescribe el align-items del contenedor solo en un hijo. Acepta los mismos valores (flex-start, center, flex-end, stretch).
.dos {
align-self: flex-end;
}
order cambia el orden visual sin tocar el HTML. Por defecto todos valen 0; un número menor va antes, uno mayor va después.
.cuatro {
order: -1;
}
order solo cambia lo que se ve; el teclado y los lectores de pantalla siguen el orden del HTML.
flex-flow resume flex-direction y flex-wrap en una sola línea.
/* Equivale a: flex-direction: row; flex-wrap: wrap; */
.fila {
flex-flow: row wrap;
}
Flex en funcionamiento
Cada caja se alinea de forma distinta y la 4 se coloca la primera.
<div class="fila">
<div class="caja uno">1</div>
<div class="caja dos">2</div>
<div class="caja tres">3</div>
<div class="caja cuatro">4</div>
</div>
.fila {
display: flex;
flex-flow: row wrap;
align-items: flex-start;
gap: 12px;
height: 170px;
padding: 12px;
background-color: #f2f4ee;
border-radius: 12px;
}
.caja {
width: 90px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: #fff;
background-color: #3a8dde;
border-radius: 8px;
}
.dos {
align-self: flex-end;
background-color: #e07a3c;
}
.tres {
align-self: center;
background-color: #4caf7a;
}
.cuatro {
order: -1;
background-color: #8e5bd6;
}
place-items, place-content y place-self
Atajos que combinan los dos ejes en una sola propiedad.
Con Grid centrar algo es de una sola línea: display: grid; place-items: center;.
/* align-items + justify-items */
.contenedor {
display: grid;
place-items: center;
}
/* align-content + justify-content */
.contenedor {
display: grid;
place-content: center;
}
/* align-self + justify-self (en un solo hijo) */
.hijo {
place-self: end;
}
<div class="celda inicio"><span></span></div>
<div class="celda centro"><span></span></div>
<div class="celda final"><span></span></div>
.celda {
display: grid;
width: 120px;
height: 120px;
background-color: #f2f4ee;
border-radius: 12px;
}
.celda span {
width: 36px;
height: 36px;
background-color: #3a8dde;
border-radius: 8px;
}
.inicio {
place-items: start;
}
.centro {
place-items: center;
}
.final {
place-items: end;
}
grid-template-areas
Dibujar el diseño con palabras.
En lugar de numerar filas y columnas, nombramos las zonas en grid-template-areas, dibujando el diseño tal y como se ve. Después cada hijo se coloca con grid-area: nombre.
- Cada texto entre comillas es una fila.
- Cada palabra es una columna.
- Repetir una palabra hace que la zona ocupe varias celdas.
- Un punto
.deja la celda vacía.
Es lo que más facilita el diseño responsive: en el @media solo redibujas las áreas.
grid-template-areas en funcionamiento
Una página completa; reduce el ancho de la ventana (menos de 600px) y las zonas se apilan.
<div class="pagina">
<header>Cabecera</header>
<nav>Menú</nav>
<main>Contenido</main>
<aside>Lateral</aside>
<footer>Pie</footer>
</div>
.pagina {
display: grid;
grid-template-columns: 150px 1fr 130px;
grid-template-rows: auto 1fr auto;
grid-template-areas: "cabecera cabecera cabecera"
"menu contenido lateral"
"pie pie pie";
gap: 10px;
min-height: 300px;
}
header {
grid-area: cabecera;
background-color: #3a8dde;
}
nav {
grid-area: menu;
background-color: #e07a3c;
}
main {
grid-area: contenido;
background-color: #4caf7a;
}
aside {
grid-area: lateral;
background-color: #8e5bd6;
}
footer {
grid-area: pie;
background-color: #364f6b;
}
@media (max-width: 600px) {
.pagina {
grid-template-columns: 1fr;
grid-template-areas: "cabecera"
"menu"
"contenido"
"lateral"
"pie";
}
}
grid-auto-rows y grid-auto-flow
Qué hace el grid con los elementos que sobran.
grid-auto-rows: alto de las filas que el grid crea automáticamente.grid-auto-flow: dense: rellena los huecos con elementos posteriores, útil cuando hay celdas de distinto tamaño.
.galeria {
/* alto de las filas que se crean solas */
grid-auto-rows: 70px;
/* rellena los huecos con los elementos siguientes */
grid-auto-flow: dense;
}
<div class="galeria">
<div class="ancho">1</div>
<div>2</div>
<div>3</div>
<div class="ancho">4</div>
<div>5</div>
<div>6</div>
<div>7</div>
</div>
.galeria {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 70px;
grid-auto-flow: dense;
gap: 8px;
}
.galeria div {
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: #fff;
background-color: #3a8dde;
border-radius: 8px;
}
.ancho {
grid-column: span 2;
background-color: #e07a3c;
}
Cuando el texto no cabe
Títulos largos, descripciones enormes y direcciones que rompen la caja: cómo controlarlo.
En una web real nunca sabemos cuánto texto va a haber. Estas propiedades evitan que el contenido rompa el diseño:
white-spaceytext-overflow: una línea con «…».line-clamp: varias líneas con «…».overflow-wrap,word-breakyhyphens: partir palabras largas.text-wrap: repartir mejor las líneas.
Una línea con puntos suspensivos
Tres propiedades que van siempre juntas.
white-space: nowrap: el texto no salta de línea.overflow: hidden: oculta lo que se sale.text-overflow: ellipsis: añade «…» al cortar.
.una-linea {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
Varias líneas con puntos suspensivos
Limitar un texto a un número de líneas.
.tres-lineas {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
Esta forma con prefijo -webkit- funciona en todos los navegadores actuales. Existe también la propiedad estándar line-clamp, que aún no se soporta en todos.
Partir palabras largas
Direcciones, correos y palabras kilométricas.
overflow-wrap: anywhere: parte una palabra solo si no cabe.word-break: break-all: parte en cualquier letra (más agresivo).hyphens: auto: parte con guion según el idioma; hay que indicarlangen el HTML.
.url {
overflow-wrap: anywhere;
}
/* alternativa más agresiva: rompe en cualquier letra */
.url {
word-break: break-all;
}
/* guiones automáticos (necesita lang="es" en el HTML) */
.guiones {
hyphens: auto;
}
text-wrap: balance y pretty
Titulares equilibrados y párrafos sin «viudas».
balance reparte el texto de un titular de forma pareja entre sus líneas. pretty evita que la última línea de un párrafo quede con una sola palabra.
h1,
h2 {
text-wrap: balance;
}
p {
text-wrap: pretty;
}
Control del texto en funcionamiento
Dos tarjetas con todas las técnicas aplicadas.
<article class="card">
<h3 class="una-linea">Un título larguísimo que no cabe en una sola línea de la tarjeta</h3>
<p class="tres-lineas">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque dignissim sapien sapien, eu dictum sapien euismod non. Nulla nec erat pulvinar, finibus urna ut, iaculis nisi. Mauris rutrum, lacus a vehicula aliquet, ex felis magna.</p>
<p class="url">https://www.ejemplo.com/una/direccion/muy/larga/que/rompe/la/caja/si/no/hacemos/nada</p>
</article>
<article class="card">
<h3 class="equilibrado">Un titular largo que se reparte de forma equilibrada en varias líneas</h3>
<p class="guiones" lang="es">Electroencefalografista, paralelepípedo y desproporcionadamente: palabras largas que se parten con guion al final de la línea.</p>
</article>
.card {
width: 250px;
padding: 16px;
border: 1px solid #d8e0e9;
border-radius: 12px;
}
.card h3 {
margin: 0 0 8px;
font-size: 1.05rem;
}
.card p {
margin: 0 0 8px;
font-size: 0.9rem;
}
/* una sola línea con puntos suspensivos */
.una-linea {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* varias líneas (3) con puntos suspensivos */
.tres-lineas {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* las palabras muy largas se parten */
.url {
overflow-wrap: anywhere;
color: #3a8dde;
}
/* reparte el titular de forma equilibrada */
.equilibrado {
text-wrap: balance;
}
/* parte las palabras con guion */
.guiones {
hyphens: auto;
text-align: justify;
}
Unidades y funciones modernas
Lo nuevo que completa a px, rem, vw y rgb().
dvh,svh,lvh: alturas de pantalla fiables en móvil.hsl()ycolor-mix(): colores más fáciles de modificar.margin-inline,padding-blockeinset: propiedades lógicas.object-position: qué parte de una imagen se ve al recortarla.
dvh, svh y lvh
El problema de 100vh en el móvil.
En el móvil, la barra del navegador aparece y desaparece al hacer scroll, y 100vh no la tiene en cuenta: una sección «a pantalla completa» queda tapada por abajo.
/* 100vh = altura de la ventana "más grande" (sin barras del navegador móvil) */
.hero-antiguo {
height: 100vh;
}
/* svh = altura con las barras visibles (la más pequeña) */
.hero-seguro {
height: 100svh;
}
/* dvh = altura dinámica: se adapta cuando las barras aparecen o se ocultan */
.hero {
height: 100dvh;
}
hsl()
Tono, saturación y luminosidad.
Con rgb() es difícil saber qué número cambiar para «hacerlo más oscuro». Con hsl() el tono es el color (0–360), la saturación su intensidad y la luminosidad lo claro u oscuro que es. Combinado con las variables de la clase 14 permite generar una paleta entera.
.boton {
/* hsl(tono 0-360, saturación %, luminosidad %) */
background-color: hsl(210 70% 55%);
}
.boton:hover {
/* solo bajamos la luminosidad */
background-color: hsl(210 70% 45%);
}
/* una paleta entera con una variable */
.muestra {
background-color: hsl(var(--tono) 70% 55%);
}
color-mix()
Mezclar dos colores directamente en CSS.
Mezcla dos colores en el porcentaje indicado. Con blanco o negro sirve para crear versiones más claras y oscuras de un color de marca.
.claro {
background-color: color-mix(in srgb, #3a8dde 30%, white);
}
.medio {
background-color: color-mix(in srgb, #3a8dde, #e07a3c);
}
Propiedades lógicas e inset
inline es el eje horizontal y block el vertical.
Son atajos y se adaptan a idiomas que se escriben de derecha a izquierda. inset: 0 sustituye a escribir top, right, bottom y left a 0 (clase 13).
.caja {
/* izquierda y derecha */
margin-inline: auto;
/* arriba y abajo */
margin-block: 12px;
padding-inline: 24px;
padding-block: 10px;
}
/* inset: top, right, bottom y left a la vez */
.capa {
position: absolute;
inset: 0;
}
object-position
Qué parte de la imagen se ve con object-fit: cover.
Con object-fit: cover (clase 21) la imagen se recorta. object-position decide qué parte se queda visible: esquina, centro…
img {
width: 100%;
height: 110px;
object-fit: cover;
object-position: left top;
}
Unidades y funciones en funcionamiento
Todo junto: pantalla, paleta, mezclas, márgenes lógicos y recortes de imagen.
<section class="hero">
<h1>Pantalla completa</h1>
<p>Alto: 50dvh</p>
</section>
<div class="paleta">
<div class="muestra" style="--tono: 10"></div>
<div class="muestra" style="--tono: 60"></div>
<div class="muestra" style="--tono: 140"></div>
<div class="muestra" style="--tono: 210"></div>
<div class="muestra" style="--tono: 290"></div>
</div>
<div class="mezclas">
<div class="mezcla claro"></div>
<div class="mezcla medio"></div>
<div class="mezcla oscuro"></div>
</div>
<div class="caja">margin-inline: auto · padding-inline · padding-block</div>
<div class="fotos">
<img src="../dinamicos/img1.svg" alt="" class="izq">
<img src="../dinamicos/img1.svg" alt="" class="der">
<img src="../dinamicos/img1.svg" alt="" class="centro">
</div>
.hero {
height: 50dvh;
display: grid;
place-content: center;
text-align: center;
background-color: #e6f5d6;
border-radius: 12px;
}
.paleta,
.mezclas,
.fotos {
display: flex;
gap: 8px;
margin-top: 12px;
}
.muestra {
flex: 1;
height: 50px;
border-radius: 8px;
background-color: hsl(var(--tono) 70% 55%);
}
.mezcla {
flex: 1;
height: 50px;
border-radius: 8px;
}
.claro {
background-color: color-mix(in srgb, #3a8dde 30%, white);
}
.medio {
background-color: color-mix(in srgb, #3a8dde, #e07a3c);
}
.oscuro {
background-color: color-mix(in srgb, #3a8dde 60%, black);
}
.caja {
width: 60%;
margin-block: 12px;
margin-inline: auto;
padding-block: 10px;
padding-inline: 24px;
text-align: center;
background-color: #f2f4ee;
border-radius: 8px;
}
.fotos img {
flex: 1;
min-width: 0;
height: 110px;
object-fit: cover;
border-radius: 8px;
}
.izq {
object-position: left top;
}
.der {
object-position: right bottom;
}
.centro {
object-position: center;
}
El problema de las tarjetas
Cada tarjeta tiene su propio grid interior y no se alinean entre sí.
Si tres tarjetas tienen un título, un texto y un botón, y un título ocupa dos líneas, el texto de esa tarjeta empieza más abajo que el de sus vecinas. Cada tarjeta es un mundo aparte.
grid-template-rows: subgrid
Las tarjetas comparten las filas del grid padre.
- Cada tarjeta ocupa varias filas del grid padre:
grid-row: span 3. - Es a su vez un grid que no define filas propias, sino que usa las del padre:
grid-template-rows: subgrid.
Así el título, el texto y el botón de todas las tarjetas quedan alineados fila a fila.
/* cada tarjeta ocupa 3 filas del grid padre y las comparte con sus vecinas */
.con .card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
row-gap: 0;
}
También funciona con columnas:
.contenedor {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
.fila-completa {
display: grid;
/* hereda las 4 columnas del padre */
grid-template-columns: subgrid;
grid-column: span 4;
}
Subgrid en funcionamiento
Arriba, sin subgrid; abajo, con subgrid.
<p class="hint">Sin subgrid</p>
<div class="tarjetas sin">
<article class="card">
<h3>Título corto</h3>
<p>Texto de la primera tarjeta.</p>
<a class="open-btn" href="#">Ver</a>
</article>
<article class="card">
<h3>Un título bastante más largo que ocupa dos líneas</h3>
<p>Texto de la segunda tarjeta, que es algo más largo que el de la primera.</p>
<a class="open-btn" href="#">Ver</a>
</article>
<article class="card">
<h3>Título medio</h3>
<p>Texto de la tercera.</p>
<a class="open-btn" href="#">Ver</a>
</article>
</div>
<p class="hint">Con subgrid</p>
<div class="tarjetas con">
<article class="card">
<h3>Título corto</h3>
<p>Texto de la primera tarjeta.</p>
<a class="open-btn" href="#">Ver</a>
</article>
<article class="card">
<h3>Un título bastante más largo que ocupa dos líneas</h3>
<p>Texto de la segunda tarjeta, que es algo más largo que el de la primera.</p>
<a class="open-btn" href="#">Ver</a>
</article>
<article class="card">
<h3>Título medio</h3>
<p>Texto de la tercera.</p>
<a class="open-btn" href="#">Ver</a>
</article>
</div>
.tarjetas {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.card {
padding: 14px;
background-color: #f6f8fa;
border: 1px solid #d8e0e9;
border-radius: 12px;
}
.card h3 {
margin: 0 0 8px;
font-size: 1rem;
}
.card p {
margin: 0 0 10px;
font-size: 0.9rem;
}
/* cada tarjeta ocupa 3 filas del grid padre y las comparte con sus vecinas */
.con .card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
row-gap: 0;
}