Saltar al contenido
maquetaciones-tipicas.css · devschool

Maquetaciones típicas en CSS: holy grail, pie pegado abajo, hero y más

Lección 25 de 50 · 14 min de lectura · Actualizado el

En esta lección
  1. El contenedor centrado
  2. Pie pegado abajo
  3. Cabecera fija
  4. Barra lateral
  5. Holy grail
  6. Hero a pantalla completa
  7. Tarjetas de la misma altura
  8. Columnas de texto
  9. ¿Qué receta uso?
  10. Errores frecuentes
  11. Resumen

La mayoría de las webs se construyen con un puñado de esquemas que se repiten: una cabecera que se queda arriba, un pie que no flota a media pantalla, una barra lateral, una portada que ocupa toda la pantalla, filas de tarjetas de la misma altura… En esta lección tienes un recetario: cada maquetación con su problema, su solución con Grid o Flexbox (o con las dos) y los detalles que hacen que funcione en el móvil.

Necesitas tener claros Flexbox, Grid y Grid a fondo. Para centrar cosas sueltas tienes su propia lección: centrar elementos.

El contenedor centrado

Casi todas las páginas limitan el ancho del contenido y lo centran, para que en una pantalla grande las líneas no sean eternas. Una sola regla:

.contenedor {
  width: min(100% - 2rem, 70rem); /* 70rem como mucho, y 1rem de aire a cada lado en el móvil */
  margin-inline: auto;
}

min() elige el menor de los dos valores: en un móvil, el ancho de la pantalla menos 2rem; en un portátil, 70rem. Lo usarás dentro de casi todas las recetas siguientes.

Pie pegado abajo

El problema: en una página con poco contenido (un “Gracias por tu compra”, un 404), el pie aparece a mitad de pantalla y por debajo queda un hueco vacío.

La idea: la página debe medir al menos el alto de la ventana, y el contenido principal se queda con todo el espacio sobrante.

<body>
  <header>Cabecera</header>
  <main>Contenido</main>
  <footer>Pie</footer>
</body>

Con Grid

body {
  min-height: 100dvh;
  margin: 0;
  display: grid;
  grid-template-rows: auto 1fr auto; /* cabecera, contenido (lo que sobre), pie */
}

La fila del medio es 1fr: se queda con todo el alto libre y empuja el pie hasta abajo. Si el contenido es largo, la página crece con normalidad y el pie va detrás.

Con Flexbox

body {
  min-height: 100dvh;
  margin: 0;
  display: flex;
  flex-direction: column;
}
main { flex-grow: 1; } /* el contenido se estira; también vale footer { margin-top: auto; } */

Las dos funcionan igual de bien. La versión de Grid tiene una pega: cuenta todos los hijos de <body>. Si hay un cuarto elemento (un aviso de cookies, un <script> no cuenta), tendrás que añadir una fila. La de Flexbox no depende del número de hijos.

Consejo: usa min-height, nunca height. Con height: 100dvh, si el contenido es más largo que la pantalla, se sale de la caja por debajo. Y prefiere dvh a vh por la barra del navegador del móvil, como viste en unidades.

Cabecera fija

El problema: quieres que el menú siga visible mientras bajas por la página.

position: sticky (la opción habitual)

.cabecera {
  position: sticky;
  top: 0;               /* se pega al llegar al borde superior */
  z-index: 10;          /* por encima del contenido que pasa por debajo */
  background: white;    /* sin fondo, verías el texto pasar a través */
}

Un elemento sticky ocupa su sitio normal en la página y, al hacer scroll, se queda pegado cuando llega a top: 0. Como no sale del flujo, no tapa el principio del contenido.

Funciona solo mientras su padre está a la vista. Por eso la cabecera debe ser hija directa de <body> (o de un contenedor tan alto como la página). Si la metes en un <div> que mide lo mismo que ella, se despega enseguida.

position: fixed (cuando debe estar siempre ahí)

.cabecera {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;      /* de lado a lado */
  height: 64px;
}
body { padding-top: 64px; } /* hueco para que no tape el principio */

fixed saca la cabecera del flujo: el resto de la página sube y queda tapado. Por eso hay que reservar el hueco a mano, sabiendo su altura. Úsalo solo si sticky no te sirve.

Los enlaces internos quedan tapados

Con cualquiera de las dos, al pulsar un enlace como #horarios la sección queda debajo de la cabecera. Se arregla en una línea:

html { scroll-padding-top: 80px; } /* el alto de la cabecera y un poco de aire */

Más detalles en scroll. Y si dentro de la cabecera colocas el logo a un lado y los enlaces al otro, es un trabajo para Flexbox: display: flex; justify-content: space-between; align-items: center;.

Barra lateral

El problema: una zona estrecha (filtros, índice, menú) junto al contenido, que en el móvil debe ir arriba o abajo.

Con Grid y una media query

.con-lateral {
  display: grid;
  gap: 24px;
}
@media (width >= 768px) {
  .con-lateral { grid-template-columns: 250px minmax(0, 1fr); }
}

En móvil hay una sola columna; desde 768px, la barra mide 250px y el contenido el resto. minmax(0, 1fr) en lugar de 1fr evita que una tabla o una URL larga ensanchen la columna y rompan el diseño.

Con Flexbox y sin media queries

Esta receta decide sola cuándo poner la barra al lado y cuándo debajo, según el espacio:

.con-lateral {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
}
.lateral {
  flex-basis: 250px;   /* ancho ideal de la barra */
  flex-grow: 1;
}
.principal {
  flex-basis: 0;
  flex-grow: 999;          /* se queda con casi todo el espacio libre */
  min-inline-size: 60%;    /* si no puede medir al menos el 60 %, baja a otra línea */
}

Mientras quepan, el contenido principal se lleva casi todo el espacio (999 partes contra 1) y la barra se queda con sus 250px. Cuando el contenido ya no puede medir el 60 % del ancho, salta a la línea siguiente y los dos se estiran al 100 %. Funciona igual dentro de un contenedor estrecho, sin depender del tamaño de la pantalla.

Barra lateral que acompaña el scroll

Un índice que se queda a la vista mientras lees un artículo largo:

.lateral {
  position: sticky;
  top: 80px;           /* por debajo de la cabecera fija */
  align-self: start;   /* imprescindible en Grid y en Flexbox */
}

Sin align-self: start, la barra se estira hasta la altura del contenido (es el comportamiento por defecto, stretch) y ya no tiene espacio para “pegarse”: parece que sticky no funciona.

Holy grail

El holy grail (santo grial) es el esquema clásico de cinco zonas: cabecera, pie y, en medio, tres columnas: menú, contenido y una barra extra. Durante años fue muy difícil hacerlo bien con CSS, de ahí el nombre. Con Grid es un dibujo:

<body>
  <header>Cabecera</header>
  <nav>Menú</nav>
  <main>Contenido</main>
  <aside>Barra extra</aside>
  <footer>Pie</footer>
</body>
body {
  min-height: 100dvh;
  margin: 0;
  display: grid;
  grid-template-rows: auto auto 1fr auto auto;
  grid-template-areas: "cabecera" "menu" "contenido" "extra" "pie";
}
@media (width >= 900px) {
  body {
    grid-template-columns: 200px minmax(0, 1fr) 200px;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
      "cabecera cabecera  cabecera"
      "menu     contenido extra"
      "pie      pie       pie";
  }
}
header { grid-area: cabecera; }
nav    { grid-area: menu; }
main   { grid-area: contenido; }
aside  { grid-area: extra; }
footer { grid-area: pie; }

Fíjate en que de regalo tienes el pie pegado abajo: la fila del contenido es 1fr y el <body> mide al menos la pantalla.

Con Flexbox

Con Flexbox necesitas un contenedor más, porque Flexbox trabaja en una sola dirección: una columna para la página y una fila para el centro.

<body>
  <header>Cabecera</header>
  <div class="centro">
    <nav>Menú</nav>
    <main>Contenido</main>
    <aside>Barra extra</aside>
  </div>
  <footer>Pie</footer>
</body>
body   { min-height: 100dvh; margin: 0; display: flex; flex-direction: column; }
.centro { flex-grow: 1; display: flex; flex-wrap: wrap; }
.centro nav, .centro aside { flex: 1 1 200px; }
.centro main { flex: 3 1 400px; }

Funciona, pero los dos laterales no se recolocan con la libertad de Grid: cuando no caben, simplemente bajan de línea. Para la estructura de la página, Grid suele ser la mejor herramienta.

Hero a pantalla completa

El hero es la portada grande con la que abren muchas webs: una imagen que ocupa toda la pantalla y un titular encima.

<section class="hero">
  <img src="img/costa.jpg" alt="Acantilados al atardecer">
  <div class="hero-texto">
    <h1>Descubre la costa</h1>
    <a class="boton" href="/rutas">Ver rutas</a>
  </div>
</section>
.hero {
  min-height: 100svh;
  display: grid;
}
.hero > * { grid-area: 1 / 1; }       /* imagen y texto en la misma celda */
.hero img {
  width: 100%;
  height: 100%;
  object-fit: cover;                  /* llena la pantalla sin deformarse */
}
.hero-texto {
  place-self: center;                 /* el texto, en el centro */
  padding: 24px;
  text-align: center;
  color: white;
  text-shadow: 0 2px 8px rgb(0 0 0 / 0.6);
}

Es la técnica de la pila de Grid a fondo: imagen y texto comparten celda y no hace falta position: absolute. Si el texto no se lee bien sobre la foto, añade un degradado oscuro en una capa más (lo tienes en degradados).

¿Por qué svh y no dvh? Los dos tienen en cuenta la barra de direcciones del móvil, pero dvh cambia de valor cuando esa barra se esconde al hacer scroll, y el hero “salta”. svh (small viewport height) usa siempre el alto con la barra visible y no cambia. Para el pie pegado abajo da igual; para algo tan grande como un hero, svh es más estable.

Cuidado: no pongas height: 100svh fijo. En un móvil apaisado, el texto del hero puede no caber y se cortaría. Con min-height, la sección crece si hace falta.

Tarjetas de la misma altura

El problema: una fila de tarjetas con textos de distinta longitud. Quieres que todas midan lo mismo y que el botón quede siempre abajo.

Con Grid (las alturas iguales salen solas)

.tarjetas {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr));
  gap: 20px;
}
.tarjeta {
  display: flex;
  flex-direction: column;   /* el contenido, en columna */
}
.tarjeta .boton {
  margin-top: auto;         /* empuja el botón al fondo */
}

Dos ideas trabajan juntas:

  1. En Grid, todas las celdas de una fila miden lo mismo que la más alta, y las tarjetas se estiran (align-items: stretch por defecto). Ya tienen la misma altura.
  2. Dentro de cada tarjeta, Flexbox en columna con margin-top: auto en el botón hace que todo el espacio libre quede encima de él: el botón baja al fondo.

Si además quieres que los títulos y los párrafos empiecen a la misma altura en todas, usa subgrid.

Con Flexbox

.tarjetas { display: flex; flex-wrap: wrap; gap: 20px; }
.tarjeta  { flex: 1 1 250px; display: flex; flex-direction: column; }
.tarjeta .boton { margin-top: auto; }

Las alturas también se igualan dentro de cada línea, pero hay una diferencia: si en la última línea quedan dos tarjetas, se estiran hasta ocupar todo el ancho y salen más anchas que las de arriba. Con Grid todas miden lo mismo. Por eso, para rejillas de tarjetas, Grid es mejor opción.

Columnas de texto

Para repartir un texto en varias columnas, como en un periódico, ni Grid ni Flexbox sirven: no saben partir un párrafo entre dos columnas. Para eso está multicolumna:

.noticia {
  columns: 3 16rem;              /* hasta 3 columnas de al menos 16rem */
  column-gap: 2rem;
  column-rule: 1px solid #cbd5e1; /* línea separadora entre columnas */
}
.noticia h2 { column-span: all; } /* el titular atraviesa todas las columnas */
.noticia figure { break-inside: avoid; } /* no partas una imagen entre columnas */

columns junta el número máximo de columnas y su ancho mínimo. En un móvil saldrá una sola, sin media queries. El texto llena la primera columna, sigue por la segunda, y así.

Úsalas con cabeza. En una pantalla, un texto largo en columnas obliga a bajar hasta el final de la primera y volver a subir para leer la segunda. Funcionan bien en bloques cortos que se ven enteros: una lista de enlaces del pie, un índice, un glosario o una lista de ingredientes.

.lista-tiendas {
  columns: 12rem;               /* tantas columnas de 12rem como quepan */
}
.lista-tiendas li { break-inside: avoid; }

Un truco: con columns y break-inside: avoid en cada tarjeta se consigue un mosaico tipo Pinterest, con tarjetas de distinta altura encajadas. La pega es que el orden va de arriba abajo por columnas, no por filas.

¿Qué receta uso?

Necesito…RecetaHerramienta
Limitar el ancho y centrarwidth: min(...) + margin-inline: autoNinguna especial
Que el pie no floteFila 1fr o flex-grow: 1 + min-height: 100dvhGrid o Flexbox
Menú siempre visibleposition: sticky; top: 0Posicionamiento
Zona lateralColumnas fijas o el truco de flex-grow: 999Grid o Flexbox
Cinco zonas clásicasHoly grail con áreasGrid
Portada a pantalla completaPila con grid-area: 1 / 1 + min-height: 100svhGrid
Tarjetas igualesauto-fill + Flexbox en columna dentroGrid + Flexbox
Texto en columnascolumnsMulticolumna

Errores frecuentes

  • Usar height: 100vh en lugar de min-height: 100dvh. El contenido largo se sale de la caja y en el móvil la barra del navegador tapa una parte.
  • Meter la cabecera sticky en un contenedor de su misma altura. El elemento sticky solo se pega dentro de su padre; si el padre es igual de alto, no tiene recorrido.
  • Olvidar el fondo de la cabecera fija. El contenido se ve pasar a través del texto.
  • Barra lateral sticky que no se pega. Le falta align-self: start: estirada a la altura del contenido, no tiene dónde moverse.
  • Usar 1fr para el contenido junto a una barra lateral y que una tabla ancha lo rompa. Usa minmax(0, 1fr).
  • Poner fixed y olvidar el hueco. El principio de la página queda debajo de la cabecera. Prefiere sticky.
  • Texto largo en columnas. Obliga a subir y bajar para leer. Resérvalas para bloques cortos.
  • Igualar alturas con height fijo. Si un texto crece, se sale de la tarjeta. Deja que Grid estire las celdas.

Resumen

MaquetaciónCódigo clave
Contenedor centradowidth: min(100% - 2rem, 70rem); margin-inline: auto;
Pie pegado abajomin-height: 100dvh; display: grid; grid-template-rows: auto 1fr auto;
Cabecera fijaposition: sticky; top: 0; z-index: 10;
Enlaces bajo la cabecerahtml { scroll-padding-top: 80px; }
Barra lateralgrid-template-columns: 250px minmax(0, 1fr);
Lateral stickyposition: sticky; top: 80px; align-self: start;
Holy grailgrid-template-areas con tres columnas y pie pegado
Heromin-height: 100svh; + pila con grid-area: 1 / 1
Tarjetas igualesGrid con auto-fill + .boton { margin-top: auto; }
Columnas de textocolumns: 3 16rem; column-span: all; break-inside: avoid;

Ya sabes construir el esqueleto de cualquier página. Ahora vamos con lo que va dentro: sigue con imágenes en CSS.

Pruébalo tú

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

maquetaciones-tipicas.html
Resultado

Pon a prueba lo que has aprendido

[CSS] Para que el pie de página quede abajo aunque haya poco contenido, ¿qué es lo más habitual?

[CSS] Tu cabecera tiene position: sticky; top: 0 pero no se pega. ¿Qué causa es más típica?

[CSS] ¿Qué soluciona html { scroll-padding-top: 80px; }?

[CSS] ¿Para qué se usa minmax(0, 1fr) en lugar de 1fr en la columna de contenido junto a una barra lateral?

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