learning-front

Nivel 1 · HTML y CSS: la estructura y la piel

Box model y Flexbox

Cómo ocupa espacio cada caja y cómo alinear y distribuir elementos en una dimensión: el box model y Flexbox son las dos herramientas que usarás en cada proyecto.

Ya sabes seleccionar cualquier elemento con precisión y darle color, tamaño y tipografía. Lo que aún no controlas es cómo ocupa espacio cada caja ni cómo distribuir varias cajas en fila. Eso es precisamente lo que cubre este capítulo: primero el box model —cómo calcula el navegador el tamaño de cada elemento—, y después Flexbox, el modelo de layout para organizar elementos en una dimensión.

En este capítulo seguimos construyendo el Overwatch Team Builder. El HTML semántico ya está hecho y la tipografía está puesta; el reto ahora es maquetar la cabecera y las cartas con box model y Flexbox.

El box model: cómo calcula el navegador el tamaño de una caja#

Cada elemento HTML es una caja rectangular. Esa caja tiene cuatro capas, de dentro a fuera:

  1. Content — el área donde va el texto o los hijos.
  2. Padding — espacio interior entre el contenido y el borde. Tiene el color de fondo del elemento.
  3. Border — el borde visible (o invisible) que rodea el padding.
  4. Margin — espacio exterior que el elemento pide a sus vecinos. No tiene fondo.

La confusión habitual viene de width. Por defecto, width describe solo el área de contenido. Si declaras width: 200px y luego añades padding: 20px, el elemento acaba midiendo 240px: el navegador suma el padding por encima del ancho declarado.

Eso es box-sizing: content-box. El comportamiento que queremos es el contrario:

css
box-sizing: border-box;

Con border-box, width incluye el padding y el borde. Si dices width: 200px, el elemento mide exactamente 200px —y el área de contenido se reduce para acomodar el padding—. Predecible y sensato. Es el primer reset que va en cualquier proyecto:

css
*,
*::before,
*::after {
  box-sizing: border-box;
}

Padding frente a margin: cuándo usar cada uno#

La regla práctica: usa padding para el espacio interior de una caja (la separación entre su borde y su contenido), y margin para separar esa caja de sus vecinos.

La diferencia se nota con el fondo: el color de fondo cubre el padding pero no el margin. En una carta de héroe, el padding es el espacio entre el borde de la tarjeta y el texto; el margin es el espacio entre una tarjeta y la siguiente.

Un aviso sobre el margin vertical: el colapso#

Hay un comportamiento del margin que sorprende a todo el mundo la primera vez, y es mejor saberlo antes de chocar con él. Si un elemento tiene margin-bottom: 24px y el de debajo margin-top: 16px, el hueco entre los dos no es 40px: es 24px, el mayor de los dos. Los márgenes verticales entre elementos en bloque no se suman: se funden en el más grande. A eso se le llama colapso de márgenes.

css
/* el hueco entre estos dos párrafos NO es 24 + 16 = 40px, sino 24px (el mayor) */
.parrafo-a {
  /* margen inferior del primer párrafo */
  margin-bottom: 24px;
}
.parrafo-b {
  /* margen superior del segundo: no se suma al de arriba, se funde con él */
  margin-top: 16px;
}

Solo pasa con márgenes verticales entre elementos en bloque: no con el padding, ni en horizontal, ni dentro de un contenedor flex (que verás justo abajo). Por ahora basta con que lo reconozcas: cuando un hueco te salga más pequeño de lo que esperabas, el colapso suele ser la causa. La forma más limpia de evitarlo es no separar con margin sino con gap en el contenedor —lo ves en la sección siguiente—, que nunca colapsa.

Flexbox: distribuir elementos en una dimensión#

Flexbox es el modelo de layout para organizar elementos en una fila o en una columna. Activas Flexbox en el contenedor:

css
.site-header {
  /* activa Flexbox: los hijos directos siguen las reglas del modelo flex */
  display: flex;
}

A partir de ahí, los hijos directos de .site-header se comportan según las reglas de Flexbox. El contenedor tiene dos ejes:

  • Eje principal — por defecto, horizontal (izquierda a derecha). Lo controlan flex-direction y justify-content.
  • Eje cruzado — perpendicular al eje principal, vertical por defecto. Lo controla align-items.

Las propiedades que más vas a usar:

PropiedadDónde vaQué hace
display: flexcontenedoractiva Flexbox
flex-directioncontenedorrow (por defecto) o column
justify-contentcontenedordistribución en el eje principal
align-itemscontenedoralineación en el eje cruzado
gapcontenedorespacio entre hijos
flex-growhijocuánto crece el hijo si sobra espacio
flex-shrinkhijocuánto se encoge si falta espacio
flex-basishijotamaño inicial antes de repartir el espacio

justify-content: cómo repartes el espacio en la fila#

Con justify-content controlas qué pasa con el espacio sobrante en el eje principal:

  • flex-start — todo apilado al inicio (por defecto)
  • flex-end — todo apilado al final
  • center — todo centrado
  • space-between — primer elemento al inicio, último al final, sobrante entre ellos
  • space-around — el sobrante se reparte con márgenes iguales alrededor de cada elemento

Para la cabecera del Team Builder —brand a la izquierda, nav a la derecha— la opción exacta es space-between.

align-items: alineación en el eje cruzado#

align-items alinea los hijos en la dirección perpendicular al eje principal. En una fila horizontal, eso significa la alineación vertical:

  • stretch — los hijos estiran su altura para ocupar todo el contenedor (por defecto)
  • flex-start — alineados al inicio (arriba en una fila horizontal)
  • center — centrados verticalmente
  • baseline — alineados por su línea base tipográfica

Para alinear el brand y los enlaces al centro vertical, align-items: center.

gap: espaciado entre hijos#

gap es la forma correcta de separar los hijos de un contenedor flex. Define el espacio entre ellos sin afectar los bordes del contenedor:

css
.hero-list {
  /* activa Flexbox en la lista: los hijos directos se colocan en fila */
  display: flex;
  /* espacio entre cada carta de héroe, sin afectar los bordes del contenedor */
  gap: 1rem;
}

La alternativa —poner margin-right en cada hijo— obliga a anular el margen del último elemento. gap no tiene ese problema: solo añade espacio entre hijos, nunca fuera del primero ni del último.

flex-grow, flex-shrink y flex-basis#

Estas tres propiedades van en los hijos y le dicen a Flexbox cómo repartir el espacio sobrante o gestionar la falta de espacio:

  • flex-grow: 1 — el elemento puede crecer para absorber el espacio libre. Si varios hijos tienen flex-grow: 1, lo reparten a partes iguales.
  • flex-shrink: 0 — el elemento no se encoge aunque falte espacio. Útil para el retrato circular: no queremos que se achate si el nombre del héroe es largo.
  • flex-basis: 14rem — el tamaño inicial del elemento antes de repartir. Es más preciso que width en un contexto flex.

La abreviatura flex: 1 equivale a flex-grow: 1; flex-shrink: 1; flex-basis: 0, y flex: none equivale a flex-grow: 0; flex-shrink: 0; flex-basis: auto.

flex-wrap: cuando los elementos no caben en una fila#

Por defecto, Flexbox intenta meter todos los hijos en una sola fila, encogiéndolos si hace falta. Con flex-wrap: wrap, los hijos que no caben saltan a la siguiente fila:

css
.hero-list {
  /* activa Flexbox en la lista */
  display: flex;
  /* permite que los hijos que no caben salten a la siguiente fila */
  flex-wrap: wrap;
  /* separación entre hijos */
  gap: 1rem;
}

.hero-list li {
  /* crece, se encoge, tamaño base 14rem */
  flex: 1 1 14rem;
}

Con esta configuración, con tres héroes salen tres columnas; si añades seis, se organizan automáticamente en dos filas de tres. Sin flex-wrap, el sexto héroe se encoge tanto que deja de ser legible.

Una nota sobre lo que se usaba antes#

Antes de que Flexbox estuviera disponible en todos los navegadores, los maquetadores usaban float (pensado para que el texto fluyera alrededor de imágenes) e inline-block para colocar elementos en fila. Seguirás viéndolos en código heredado. No merece la pena aprenderlos como técnica de layout: Flexbox y Grid los reemplazan por completo, y si algún día te los encuentras, las DevTools te darán contexto suficiente para entenderlos.

Pruébalo#

Esta es la cabecera y las cartas del Team Builder ya maquetadas. Abre la pestaña /styles.css y modifica los valores que se mencionan en los comentarios del HTML: cambia justify-content, activa o desactiva flex-wrap, observa qué hace flex: none en el retrato. Los cambios son inmediatos en el preview.

Comprueba lo que sabes#

Pregunta 1 de 5

Declaras `width: 200px` y `padding: 20px` en un elemento. ¿Cuánto mide en total si NO has puesto `box-sizing: border-box`?

Tu turno#

El HTML ya está hecho. Tu tarea es completar los TODO del fichero CSS, aquí mismo. Escribe las reglas de box model y Flexbox para que la cabecera y las cartas queden bien maquetadas. Cuando creas que lo tienes, despliega las soluciones y compáralas con la tuya.

Ejercicio · en esta página

Maqueta la cabecera y las cartas del Team Builder

El HTML de la página ya está hecho: una cabecera con su navegación y una lista de tres cartas de héroe. Tu tarea es escribir el CSS para maquetar esa estructura con box model y Flexbox. Completa los TODO de starter/styles.css hasta que la página se vea limpia y bien espaciada.

Paso 1: Que funcione

  • La cabecera muestra brand y nav en fila.
  • Las tres cartas se ven en fila, con retrato, nombre, rol y estadísticas.
  • Hay padding y bordes en las cartas, y separación entre las estadísticas.
Ver soluciones
/* =============================================================================
   NIVEL OK — "funciona y se ve"
   =============================================================================
   Resuelve todos los TODO del starter. La página se ve correctamente.

   Sus límites (lo que arregla el nivel "Mejor"):
     - Números mágicos por todas partes: 48px, 20px, 220px... Si el diseño
       cambia, hay que cazar esos valores uno a uno por todo el fichero.
     - Espaciado a base de margin sueltos en lugar de gap. El resultado es el
       mismo, pero gap es la herramienta correcta en un contenedor flex/grid.
     - El retrato y la carta tienen ancho y alto fijos en píxeles: funcionan
       para estos tres héroes, pero con un nombre largo como "Brigitte Lindholm"
       la tarjeta se rompe.
     - box-sizing no está declarado globalmente: el padding suma al tamaño, así
       que los cálculos de ancho son frágiles.
   Aun así: se ve y ocupa el espacio correcto. Es un punto de partida válido.
   ============================================================================= */

body {
  margin: 0;
  font-family:
    system-ui,
    -apple-system,
    "Segoe UI",
    Roboto,
    sans-serif;
  background: #f7f6fb;
  color: #1c1b22;
  line-height: 1.5;
}

.page {
  max-width: 960px;
  margin: 0 auto;
  padding: 40px 24px 64px;
}

/* --- Cabecera --- */

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid #e6e3ee;
  padding-bottom: 20px;
  margin-bottom: 32px;
}

.brand {
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0;
}

.brand span {
  color: #b8336a;
}

.site-nav a {
  color: #5b5966;
  text-decoration: none;
  font-size: 0.9rem;
  margin-left: 16px;
}

.site-nav a:hover {
  color: #b8336a;
}

/* --- Lista de héroes --- */

.section-title {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #5b5966;
  margin: 0 0 16px;
}

.hero-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
}

.hero-list li {
  margin-right: 16px;
  width: 220px;
}

/* --- Carta de héroe --- */

.hero-card {
  background: #fff;
  border: 1px solid #e6e3ee;
  border-radius: 12px;
  padding: 20px;
}

.hero-top {
  display: flex;
  align-items: center;
}

.hero-portrait {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: #b8336a;
  color: #fff;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 14px;
}

.hero-name {
  font-size: 1.05rem;
  font-weight: 700;
  margin: 0;
}

.hero-role {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #5b5966;
  margin: 0.1rem 0 0;
}

.hero-stats {
  display: flex;
  margin: 12px 0 0;
  padding-top: 8px;
  border-top: 1px solid #e6e3ee;
}

.stat {
  display: flex;
  flex-direction: column;
  margin-right: 20px;
}

.stat dt {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #5b5966;
  margin: 0;
}

.stat dd {
  font-size: 1.05rem;
  font-weight: 600;
  margin: 0.15rem 0 0;
}

/* --- Pie --- */

.site-footer {
  margin-top: 3rem;
  padding-top: 1.25rem;
  border-top: 1px solid #e6e3ee;
  color: #5b5966;
  font-size: 0.85rem;
}

Por qué este nivel

  • Resuelve todos los TODO y la página se ve correctamente. Ese es el objetivo mínimo.
  • Usa `margin` suelto para separar elementos en lugar de `gap`, y hay números mágicos en px repartidos por el fichero (48px, 220px, 20px...).
  • Sin `box-sizing: border-box` global: el padding suma al `width` declarado, así que el tamaño real de las cajas es difícil de predecir.
  • Con anchos fijos en las cartas y el retrato sin `flex: none`, un nombre largo como "Brigitte Lindholm" descolocaría el layout.